diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 94d21ed70e..e80f928a45 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -780,7 +780,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\ConstantScalarType is error\-prone and deprecated\. Use Type\:\:isConstantScalarValue\(\) or Type\:\:getConstantScalarTypes\(\) or Type\:\:getConstantScalarValues\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 3 + count: 2 path: src/Testing/TypeInferenceTestCase.php - @@ -2022,7 +2022,7 @@ parameters: - message: '#^Access to constant on internal class PHPUnit\\Framework\\AssertionFailedError\.$#' identifier: classConstant.internalClass - count: 1 + count: 2 path: tests/PHPStan/Testing/TypeInferenceTestCaseTest.php - diff --git a/src/Testing/TypeInferenceTestCase.php b/src/Testing/TypeInferenceTestCase.php index 1793d7b1ec..227ab22fe1 100644 --- a/src/Testing/TypeInferenceTestCase.php +++ b/src/Testing/TypeInferenceTestCase.php @@ -17,6 +17,7 @@ use PHPStan\Php\PhpVersion; use PHPStan\PhpDoc\PhpDocInheritanceResolver; use PHPStan\PhpDoc\StubPhpDocProvider; +use PHPStan\PhpDoc\TypeStringResolver; use PHPStan\Reflection\AttributeReflectionFactory; use PHPStan\Reflection\Deprecation\DeprecationProvider; use PHPStan\Reflection\InitializerExprTypeResolver; @@ -203,12 +204,13 @@ public static function gatherAssertTypes(string $file): array $relativePathHelper = new SystemAgnosticSimpleRelativePathHelper($fileHelper); $reflectionProvider = self::getContainer()->getByType(ReflectionProvider::class); + $typeStringResolver = self::getContainer()->getByType(TypeStringResolver::class); $file = $fileHelper->normalizePath($file); $asserts = []; $delayedErrors = []; - self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, &$delayedErrors, $file, $relativePathHelper, $reflectionProvider): void { + self::processFile($file, static function (Node $node, Scope $scope) use (&$asserts, &$delayedErrors, $file, $relativePathHelper, $reflectionProvider, $typeStringResolver): void { if ($node instanceof InClassNode) { if (!$reflectionProvider->hasClass($node->getClassReflection()->getName())) { $delayedErrors[] = sprintf( @@ -270,7 +272,8 @@ public static function gatherAssertTypes(string $file): array $assert = ['type', $file, $expectedType->getValue(), $actualType->describe(VerbosityLevel::precise()), $node->getStartLine()]; } elseif ($functionName === 'PHPStan\\Testing\\assertSuperType') { $expectedType = $scope->getType($node->getArgs()[0]->value); - if (!$expectedType instanceof ConstantScalarType) { + $expectedTypeStrings = $expectedType->getConstantStrings(); + if (count($expectedTypeStrings) !== 1) { self::fail(sprintf( 'Expected super type must be a literal string, %s given in %s on line %d.', $expectedType->describe(VerbosityLevel::precise()), @@ -278,8 +281,11 @@ public static function gatherAssertTypes(string $file): array $node->getStartLine(), )); } + $actualType = $scope->getType($node->getArgs()[1]->value); - $assert = ['superType', $file, $expectedType->getValue(), $actualType->describe(VerbosityLevel::precise()), $expectedType->isSuperTypeOf($actualType)->yes(), $node->getStartLine()]; + $isCorrect = $typeStringResolver->resolve($expectedTypeStrings[0]->getValue())->isSuperTypeOf($actualType)->yes(); + + $assert = ['superType', $file, $expectedTypeStrings[0]->getValue(), $actualType->describe(VerbosityLevel::precise()), $isCorrect, $node->getStartLine()]; } elseif ($functionName === 'PHPStan\\Testing\\assertVariableCertainty') { $certainty = $node->getArgs()[0]->value; if (!$certainty instanceof StaticCall) { diff --git a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php index cce8bd958a..054094eebb 100644 --- a/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php +++ b/tests/PHPStan/Testing/TypeInferenceTestCaseTest.php @@ -37,6 +37,13 @@ public static function dataFileAssertionFailedErrors(): iterable $fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-type-missing-namespace.php'), ), ]; + yield [ + __DIR__ . '/data/assert-super-type-missing-namespace.php', + sprintf( + 'Missing use statement for assertSuperType() in %s on line 6.', + $fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-super-type-missing-namespace.php'), + ), + ]; yield [ __DIR__ . '/data/assert-certainty-wrong-namespace.php', sprintf( @@ -58,6 +65,13 @@ public static function dataFileAssertionFailedErrors(): iterable $fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-type-wrong-namespace.php'), ), ]; + yield [ + __DIR__ . '/data/assert-super-type-wrong-namespace.php', + sprintf( + 'Function PHPStan\Testing\assertSuperType imported with wrong namespace SomeWrong\Namespace\assertSuperType called in %s on line 8.', + $fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php'), + ), + ]; yield [ __DIR__ . '/data/assert-certainty-case-insensitive.php', sprintf( @@ -79,6 +93,13 @@ public static function dataFileAssertionFailedErrors(): iterable $fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-type-case-insensitive.php'), ), ]; + yield [ + __DIR__ . '/data/assert-super-type-case-insensitive.php', + sprintf( + 'Missing use statement for assertSuperTYPe() in %s on line 6.', + $fileHelper->normalizePath('tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php'), + ), + ]; } #[DataProvider('dataFileAssertionFailedErrors')] @@ -100,6 +121,28 @@ public function testVariableOrOffsetDescription(): void $this->assertSame("offset 'email'", $offsetAssert[4]); } + public function testSuperType(): void + { + foreach (self::gatherAssertTypes(__DIR__ . '/data/assert-super-type.php') as $data) { + $this->assertFileAsserts(...$data); + } + } + + public static function dataSuperTypeFailed(): array + { + return self::gatherAssertTypes(__DIR__ . '/data/assert-super-type-failed.php'); + } + + /** + * @param mixed ...$args + */ + #[DataProvider('dataSuperTypeFailed')] + public function testSuperTypeFailed(...$args): void + { + $this->expectException(AssertionFailedError::class); + $this->assertFileAsserts(...$args); + } + public function testNonexistentClassInAnalysedFile(): void { foreach (self::gatherAssertTypes(__DIR__ . '/../../notAutoloaded/nonexistentClasses.php') as $data) { diff --git a/tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php b/tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php new file mode 100644 index 0000000000..40137f9dad --- /dev/null +++ b/tests/PHPStan/Testing/data/assert-super-type-case-insensitive.php @@ -0,0 +1,8 @@ += 8.0 + +namespace MissingTypeCaseSensitive; + +function doFoo(string $s) { + assertSuperTYPe('string', $s); +} + diff --git a/tests/PHPStan/Testing/data/assert-super-type-failed.php b/tests/PHPStan/Testing/data/assert-super-type-failed.php new file mode 100644 index 0000000000..6c6f8dd5e6 --- /dev/null +++ b/tests/PHPStan/Testing/data/assert-super-type-failed.php @@ -0,0 +1,9 @@ += 8.0 + +namespace MissingAssertTypeNamespace; + +function doFoo(string $s) { + assertSuperType('string', $s); +} + diff --git a/tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php b/tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php new file mode 100644 index 0000000000..6a6afcc511 --- /dev/null +++ b/tests/PHPStan/Testing/data/assert-super-type-wrong-namespace.php @@ -0,0 +1,10 @@ += 8.0 + +namespace WrongAssertTypeNamespace; + +use function SomeWrong\Namespace\assertSuperType; + +function doFoo(string $s) { + assertSuperType('string', $s); +} + diff --git a/tests/PHPStan/Testing/data/assert-super-type.php b/tests/PHPStan/Testing/data/assert-super-type.php new file mode 100644 index 0000000000..1270192d57 --- /dev/null +++ b/tests/PHPStan/Testing/data/assert-super-type.php @@ -0,0 +1,8 @@ +