From 299eca4daa08664cdd4af8127b3934663d974f63 Mon Sep 17 00:00:00 2001 From: Pete Walker Date: Fri, 21 Jan 2022 17:31:48 +0000 Subject: [PATCH] fix: Add PHP version checks / more tests --- .../Reflector/ClassLikeNodeScanner.php | 19 +-- .../Reflector/FunctionLikeNodeScanner.php | 8 ++ .../PhpVisitor/Reflector/TypeHintResolver.php | 35 ++++++ tests/NativeIntersectionsTest.php | 42 +++++++ tests/NativeUnionsTest.php | 119 ++++++++++++++++++ 5 files changed, 214 insertions(+), 9 deletions(-) create mode 100644 tests/NativeUnionsTest.php diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index 4ae30b5f4..4c77a4ae1 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -1488,15 +1488,6 @@ class ClassLikeNodeScanner $parser_property_type = $stmt->type; /** @var Identifier|IntersectionType|Name|NullableType|UnionType $parser_property_type */ - $signature_type = TypeHintResolver::resolve( - $parser_property_type, - $this->codebase, - $this->file_storage, - $this->storage, - $this->aliases, - $this->codebase->analysis_php_version_id - ); - $signature_type_location = new CodeLocation( $this->file_scanner, $parser_property_type, @@ -1504,6 +1495,16 @@ class ClassLikeNodeScanner false, CodeLocation::FUNCTION_RETURN_TYPE ); + + $signature_type = TypeHintResolver::resolve( + $parser_property_type, + $signature_type_location, + $this->codebase, + $this->file_storage, + $this->storage, + $this->aliases, + $this->codebase->analysis_php_version_id + ); } $doc_var_group_type = $var_comment->type ?? null; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index 077445c09..b67940138 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -430,6 +430,10 @@ class FunctionLikeNodeScanner $storage->return_type = TypeHintResolver::resolve( $original_type, + new CodeLocation( + $this->file_scanner, + $original_type + ), $this->codebase, $this->file_storage, $this->classlike_storage, @@ -825,6 +829,10 @@ class FunctionLikeNodeScanner $param_type = TypeHintResolver::resolve( $param_typehint, + new CodeLocation( + $this->file_scanner, + $param_typehint + ), $this->codebase, $this->file_storage, $this->classlike_storage, diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php index f11b97a87..8293f70b3 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php @@ -10,7 +10,10 @@ use PhpParser\Node\NullableType; use PhpParser\Node\UnionType; use Psalm\Aliases; use Psalm\Codebase; +use Psalm\CodeLocation; use Psalm\Internal\Analyzer\ClassLikeAnalyzer; +use Psalm\Issue\ParseError; +use Psalm\IssueBuffer; use Psalm\Storage\ClassLikeStorage; use Psalm\Storage\FileStorage; use Psalm\Type; @@ -31,6 +34,7 @@ class TypeHintResolver */ public static function resolve( PhpParser\NodeAbstract $hint, + CodeLocation $code_location, Codebase $codebase, FileStorage $file_storage, ?ClassLikeStorage $classlike_storage, @@ -44,9 +48,19 @@ class TypeHintResolver throw new UnexpectedValueException('bad'); } + if ($analysis_php_version_id < 8_00_00) { + IssueBuffer::add( + new ParseError( + 'Union types are not supported in PHP < 8', + $code_location + ) + ); + } + foreach ($hint->types as $atomic_typehint) { $resolved_type = self::resolve( $atomic_typehint, + $code_location, $codebase, $file_storage, $classlike_storage, @@ -67,9 +81,20 @@ class TypeHintResolver throw new UnexpectedValueException('bad'); } + if ($analysis_php_version_id < 8_10_00) { + IssueBuffer::add( + new ParseError( + 'Intersection types are not supported in PHP < 8.1', + $code_location + ) + ); + return false; + } + foreach ($hint->types as $atomic_typehint) { $resolved_type = self::resolve( $atomic_typehint, + $code_location, $codebase, $file_storage, $classlike_storage, @@ -77,6 +102,16 @@ class TypeHintResolver $analysis_php_version_id ); + if ($resolved_type->hasScalarType()) { + IssueBuffer::add( + new ParseError( + 'Intersection types cannot contain scalar types', + $code_location + ) + ); + return null; + } + $type = Type::intersectUnionTypes($resolved_type, $type, $codebase); } diff --git a/tests/NativeIntersectionsTest.php b/tests/NativeIntersectionsTest.php index 443cf2ba3..f16f57fe5 100644 --- a/tests/NativeIntersectionsTest.php +++ b/tests/NativeIntersectionsTest.php @@ -103,6 +103,48 @@ class NativeIntersectionsTest extends TestCase 'ignored_issues' => [], 'php_version' => '8.1' ], + 'intersectionsNotAllowedWithUnions' => [ + ' 'ParseError', + [], + false, + '8.1' + ], + 'intersectionsNotAllowedWithNonClasses' => [ + ' 'ParseError', + [], + false, + '8.1' + ], + 'intersectionsNotAllowedInPHP80' => [ + ' 'ParseError', + [], + false, + '8.0' + ], ]; } } diff --git a/tests/NativeUnionsTest.php b/tests/NativeUnionsTest.php new file mode 100644 index 000000000..6ee8ef712 --- /dev/null +++ b/tests/NativeUnionsTest.php @@ -0,0 +1,119 @@ +,error_levels?:string[],php_version?:string}> + */ + public function providerValidCodeParse(): iterable + { + return [ + 'nativeTypeUnionInConstructor' => [ + 'self; + } + }', + 'assertions' => [], + 'error_levels' => [], + 'php_version' => '8.0' + ], + 'nativeTypeUnionAsArgument' => [ + 'foo(); + } + test(new C()); + ', + 'assertions' => [], + 'error_levels' => [], + 'php_version' => '8.0' + ], + 'unionAndNullableEquivalent' => [ + ' [], + 'error_levels' => [], + 'php_version' => '8.0' + ], + ]; + } + + /** + * @return iterable + */ + public function providerInvalidCodeParse(): iterable + { + return [ + 'invalidNativeUnionArgument' => [ + ' 'InvalidScalarArgument', + [], + false, + '8.0' + ], + 'mismatchDocblockNativeUnionArgument' => [ + ' 'MismatchingDocblockParamType', + [], + false, + '8.0' + ], + 'unionsNotAllowedInPHP74' => [ + ' 'ParseError', + [], + false, + '7.4' + ], + ]; + } +}