From 3feb236ccaacb0ce7b9ff9a873a97260a9fc736d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 26 Jan 2025 13:58:51 +0100 Subject: [PATCH 1/8] Remove purity check from native type --- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 36 +++++++++++++------ .../VarTagChangedExpressionTypeRuleTest.php | 7 +++- .../WrongVariableNameInVarTagRuleTest.php | 12 ++++++- tests/PHPStan/Rules/PhpDoc/data/bug-12458.php | 17 +++++++++ 4 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 tests/PHPStan/Rules/PhpDoc/data/bug-12458.php diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 0070554896..b15b74b18a 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -4,11 +4,14 @@ use PhpParser\Node; use PhpParser\Node\Expr; +use PHPStan\Analyser\NameScope; use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; use PHPStan\PhpDoc\Tag\VarTag; +use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; +use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; @@ -24,7 +27,11 @@ final class VarTagTypeRuleHelper { - public function __construct(private bool $checkTypeAgainstPhpDocType, private bool $strictWideningCheck) + public function __construct( + private TypeNodeResolver $typeNodeResolver, + private bool $checkTypeAgainstPhpDocType, + private bool $strictWideningCheck, + ) { } @@ -134,15 +141,15 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v $type = new ArrayType(new MixedType(), new MixedType()); } - return $type->isSuperTypeOf($varTagType)->no(); + return $this->isSuperTypeOfVarType($type, $varTagType)->no(); } if ($expr instanceof Expr\ConstFetch) { - return $type->isSuperTypeOf($varTagType)->no(); + return $this->isSuperTypeOfVarType($type, $varTagType)->no(); } if ($expr instanceof Node\Scalar) { - return $type->isSuperTypeOf($varTagType)->no(); + return $this->isSuperTypeOfVarType($type, $varTagType)->no(); } if ($expr instanceof Expr\New_) { @@ -157,18 +164,18 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v private function checkType(Type $type, Type $varTagType, int $depth = 0): bool { if ($this->strictWideningCheck) { - return !$type->isSuperTypeOf($varTagType)->yes(); + return !$this->isSuperTypeOfVarType($type, $varTagType)->yes(); } if ($type->isConstantArray()->yes()) { if ($type->isIterableAtLeastOnce()->no()) { $type = new ArrayType(new MixedType(), new MixedType()); - return $type->isSuperTypeOf($varTagType)->no(); + return $this->isSuperTypeOfVarType($type, $varTagType)->no(); } } if ($type->isIterable()->yes() && $varTagType->isIterable()->yes()) { - if ($type->isSuperTypeOf($varTagType)->no()) { + if ($this->isSuperTypeOfVarType($type, $varTagType)->no()) { return true; } @@ -176,17 +183,24 @@ private function checkType(Type $type, Type $varTagType, int $depth = 0): bool $innerVarTagType = $varTagType->getIterableValueType(); if ($type->equals($innerType) || $varTagType->equals($innerVarTagType)) { - return !$innerType->isSuperTypeOf($innerVarTagType)->yes(); + return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType)->yes(); } return $this->checkType($innerType, $innerVarTagType, $depth + 1); } - if ($type->isConstantValue()->yes() && $depth === 0) { - return $type->isSuperTypeOf($varTagType)->no(); + if ($depth === 0 && $type->isConstantValue()->yes()) { + return $this->isSuperTypeOfVarType($type, $varTagType)->no(); } - return !$type->isSuperTypeOf($varTagType)->yes(); + return !$this->isSuperTypeOfVarType($type, $varTagType)->yes(); + } + + private function isSuperTypeOfVarType(Type $type, Type $varTagType): TrinaryLogic + { + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, [])); + + return $type->isSuperTypeOf($varTagType); } } diff --git a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php index f3ea56d12f..7a5d611a77 100644 --- a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; @@ -13,7 +14,11 @@ class VarTagChangedExpressionTypeRuleTest extends RuleTestCase protected function getRule(): Rule { - return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper(true, true)); + return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper( + self::getContainer()->getByType(TypeNodeResolver::class), + true, + true, + )); } public function testRule(): void diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index e25e8df924..ea5f95eb06 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -2,6 +2,7 @@ namespace PHPStan\Rules\PhpDoc; +use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use PHPStan\Type\FileTypeMapper; @@ -23,7 +24,11 @@ protected function getRule(): Rule { return new WrongVariableNameInVarTagRule( self::getContainer()->getByType(FileTypeMapper::class), - new VarTagTypeRuleHelper($this->checkTypeAgainstPhpDocType, $this->strictWideningCheck), + new VarTagTypeRuleHelper( + self::getContainer()->getByType(TypeNodeResolver::class), + $this->checkTypeAgainstPhpDocType, + $this->strictWideningCheck, + ), $this->checkTypeAgainstNativeType, ); } @@ -182,6 +187,11 @@ public function testBug4505(): void $this->analyse([__DIR__ . '/data/bug-4505.php'], []); } + public function testBug12458(): void + { + $this->analyse([__DIR__ . '/data/bug-12458.php'], []); + } + public function testEnums(): void { if (PHP_VERSION_ID < 80100) { diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php b/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php new file mode 100644 index 0000000000..35a3ba8f37 --- /dev/null +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php @@ -0,0 +1,17 @@ + $a + */ + public function test(array $a): void + { + /** @var \Closure(): list $c */ + $c = function () use ($a): array { + return $a; + }; + } +} From d5c84132de0385a9ef33a1782b59db444f006a0d Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sun, 26 Jan 2025 22:21:05 +0100 Subject: [PATCH 2/8] Refacto --- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 29 ++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index b15b74b18a..8e3138ef25 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -141,15 +141,15 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v $type = new ArrayType(new MixedType(), new MixedType()); } - return $this->isSuperTypeOfVarType($type, $varTagType)->no(); + return !$this->isSuperTypeOfVarType($type, $varTagType, false); } if ($expr instanceof Expr\ConstFetch) { - return $this->isSuperTypeOfVarType($type, $varTagType)->no(); + return !$this->isSuperTypeOfVarType($type, $varTagType, false); } if ($expr instanceof Node\Scalar) { - return $this->isSuperTypeOfVarType($type, $varTagType)->no(); + return !$this->isSuperTypeOfVarType($type, $varTagType, false); } if ($expr instanceof Expr\New_) { @@ -164,18 +164,18 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v private function checkType(Type $type, Type $varTagType, int $depth = 0): bool { if ($this->strictWideningCheck) { - return !$this->isSuperTypeOfVarType($type, $varTagType)->yes(); + return !$this->isSuperTypeOfVarType($type, $varTagType, true); } if ($type->isConstantArray()->yes()) { if ($type->isIterableAtLeastOnce()->no()) { $type = new ArrayType(new MixedType(), new MixedType()); - return $this->isSuperTypeOfVarType($type, $varTagType)->no(); + return !$this->isSuperTypeOfVarType($type, $varTagType, false); } } if ($type->isIterable()->yes() && $varTagType->isIterable()->yes()) { - if ($this->isSuperTypeOfVarType($type, $varTagType)->no()) { + if (!$this->isSuperTypeOfVarType($type, $varTagType, false)) { return true; } @@ -183,24 +183,31 @@ private function checkType(Type $type, Type $varTagType, int $depth = 0): bool $innerVarTagType = $varTagType->getIterableValueType(); if ($type->equals($innerType) || $varTagType->equals($innerVarTagType)) { - return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType)->yes(); + return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType, true); } return $this->checkType($innerType, $innerVarTagType, $depth + 1); } if ($depth === 0 && $type->isConstantValue()->yes()) { - return $this->isSuperTypeOfVarType($type, $varTagType)->no(); + return !$this->isSuperTypeOfVarType($type, $varTagType, false); } - return !$this->isSuperTypeOfVarType($type, $varTagType)->yes(); + return !$this->isSuperTypeOfVarType($type, $varTagType, true); } - private function isSuperTypeOfVarType(Type $type, Type $varTagType): TrinaryLogic + private function isSuperTypeOfVarType(Type $type, Type $varTagType, bool $strict): bool { + $validationCallable = static fn (TrinaryLogic $trinaryLogic): bool => $strict ? $trinaryLogic->yes() : !$trinaryLogic->no(); + + $result = $type->isSuperTypeOf($varTagType); + if ($validationCallable($result)) { + return true; + } + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, [])); - return $type->isSuperTypeOf($varTagType); + return $validationCallable($type->isSuperTypeOf($varTagType)); } } From 0ae5874e19768d038532f0183e5648278edf419c Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 28 Jan 2025 00:02:28 +0100 Subject: [PATCH 3/8] Refacto --- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 37 ++++++++++++++--------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 8e3138ef25..1fda953062 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -11,7 +11,6 @@ use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; -use PHPStan\TrinaryLogic; use PHPStan\Type\ArrayType; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; @@ -141,15 +140,15 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v $type = new ArrayType(new MixedType(), new MixedType()); } - return !$this->isSuperTypeOfVarType($type, $varTagType, false); + return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); } if ($expr instanceof Expr\ConstFetch) { - return !$this->isSuperTypeOfVarType($type, $varTagType, false); + return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); } if ($expr instanceof Node\Scalar) { - return !$this->isSuperTypeOfVarType($type, $varTagType, false); + return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); } if ($expr instanceof Expr\New_) { @@ -164,18 +163,18 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v private function checkType(Type $type, Type $varTagType, int $depth = 0): bool { if ($this->strictWideningCheck) { - return !$this->isSuperTypeOfVarType($type, $varTagType, true); + return !$this->isSuperTypeOfVarType($type, $varTagType); } if ($type->isConstantArray()->yes()) { if ($type->isIterableAtLeastOnce()->no()) { $type = new ArrayType(new MixedType(), new MixedType()); - return !$this->isSuperTypeOfVarType($type, $varTagType, false); + return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); } } if ($type->isIterable()->yes() && $varTagType->isIterable()->yes()) { - if (!$this->isSuperTypeOfVarType($type, $varTagType, false)) { + if (!$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType)) { return true; } @@ -183,31 +182,39 @@ private function checkType(Type $type, Type $varTagType, int $depth = 0): bool $innerVarTagType = $varTagType->getIterableValueType(); if ($type->equals($innerType) || $varTagType->equals($innerVarTagType)) { - return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType, true); + return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType); } return $this->checkType($innerType, $innerVarTagType, $depth + 1); } if ($depth === 0 && $type->isConstantValue()->yes()) { - return !$this->isSuperTypeOfVarType($type, $varTagType, false); + return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); } - return !$this->isSuperTypeOfVarType($type, $varTagType, true); + return !$this->isSuperTypeOfVarType($type, $varTagType); } - private function isSuperTypeOfVarType(Type $type, Type $varTagType, bool $strict): bool + private function isSuperTypeOfVarType(Type $type, Type $varTagType): bool { - $validationCallable = static fn (TrinaryLogic $trinaryLogic): bool => $strict ? $trinaryLogic->yes() : !$trinaryLogic->no(); + if ($type->isSuperTypeOf($varTagType)->yes()) { + return true; + } + + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, [])); - $result = $type->isSuperTypeOf($varTagType); - if ($validationCallable($result)) { + return $type->isSuperTypeOf($varTagType)->yes(); + } + + private function isAtLeastMaybeSuperTypeOfVarType(Type $type, Type $varTagType): bool + { + if (!$type->isSuperTypeOf($varTagType)->no()) { return true; } $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, [])); - return $validationCallable($type->isSuperTypeOf($varTagType)); + return !$type->isSuperTypeOf($varTagType)->no(); } } From 1719af01f82dcc98c80175729ab46453deacab5b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 24 Feb 2025 09:56:00 +0100 Subject: [PATCH 4/8] Add test about generic --- tests/PHPStan/Rules/PhpDoc/data/bug-12458.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php b/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php index 35a3ba8f37..08e7e1c710 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php +++ b/tests/PHPStan/Rules/PhpDoc/data/bug-12458.php @@ -1,6 +1,6 @@ $a + */ + public function testGeneric(array $a): void + { + /** @var \Closure(): list $c */ + $c = function () use ($a): array { + return $a; + }; + } } From 2b8dae7a700280e77f4af078b2973f28fc744083 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 25 Feb 2025 17:44:51 +0100 Subject: [PATCH 5/8] Fix test --- .../Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index ea5f95eb06..3380dca4da 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -189,6 +189,10 @@ public function testBug4505(): void public function testBug12458(): void { + $this->checkTypeAgainstNativeType = true; + $this->checkTypeAgainstPhpDocType = true; + $this->strictWideningCheck = true; + $this->analyse([__DIR__ . '/data/bug-12458.php'], []); } From 58be765ed3ca76f8660aa65873685ee18c26cf72 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 26 Feb 2025 14:14:11 +0100 Subject: [PATCH 6/8] Fix --- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 64 +++++++++++++------ .../VarTagChangedExpressionTypeRuleTest.php | 2 + .../WrongVariableNameInVarTagRuleTest.php | 1 + 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 1fda953062..0b1dfbc8ac 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -7,11 +7,13 @@ use PHPStan\Analyser\NameScope; use PHPStan\Analyser\Scope; use PHPStan\Node\Expr\GetOffsetValueTypeExpr; +use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException; use PHPStan\PhpDoc\Tag\VarTag; use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\IdentifierRuleError; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ArrayType; +use PHPStan\Type\FileTypeMapper; use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; @@ -28,6 +30,7 @@ final class VarTagTypeRuleHelper public function __construct( private TypeNodeResolver $typeNodeResolver, + private FileTypeMapper $fileTypeMapper, private bool $checkTypeAgainstPhpDocType, private bool $strictWideningCheck, ) @@ -82,7 +85,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): $errors = []; $exprNativeType = $scope->getNativeType($expr); $containsPhpStanType = $this->containsPhpStanType($varTagType); - if ($this->shouldVarTagTypeBeReported($expr, $exprNativeType, $varTagType)) { + if ($this->shouldVarTagTypeBeReported($scope, $expr, $exprNativeType, $varTagType)) { $verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType); $errors[] = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @var with type %s is not subtype of native type %s.', @@ -92,7 +95,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): } else { $exprType = $scope->getType($expr); if ( - $this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType) + $this->shouldVarTagTypeBeReported($scope, $expr, $exprType, $varTagType) && ($this->checkTypeAgainstPhpDocType || $containsPhpStanType) ) { $verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType); @@ -133,22 +136,22 @@ private function containsPhpStanType(Type $type): bool return false; } - private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $varTagType): bool + private function shouldVarTagTypeBeReported(Scope $scope, Node\Expr $expr, Type $type, Type $varTagType): bool { if ($expr instanceof Expr\Array_) { if ($expr->items === []) { $type = new ArrayType(new MixedType(), new MixedType()); } - return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } if ($expr instanceof Expr\ConstFetch) { - return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } if ($expr instanceof Node\Scalar) { - return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } if ($expr instanceof Expr\New_) { @@ -157,24 +160,24 @@ private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $v } } - return $this->checkType($type, $varTagType); + return $this->checkType($scope, $type, $varTagType); } - private function checkType(Type $type, Type $varTagType, int $depth = 0): bool + private function checkType(Scope $scope, Type $type, Type $varTagType, int $depth = 0): bool { if ($this->strictWideningCheck) { - return !$this->isSuperTypeOfVarType($type, $varTagType); + return !$this->isSuperTypeOfVarType($scope, $type, $varTagType); } if ($type->isConstantArray()->yes()) { if ($type->isIterableAtLeastOnce()->no()) { $type = new ArrayType(new MixedType(), new MixedType()); - return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } } if ($type->isIterable()->yes() && $varTagType->isIterable()->yes()) { - if (!$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType)) { + if (!$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType)) { return true; } @@ -182,39 +185,62 @@ private function checkType(Type $type, Type $varTagType, int $depth = 0): bool $innerVarTagType = $varTagType->getIterableValueType(); if ($type->equals($innerType) || $varTagType->equals($innerVarTagType)) { - return !$this->isSuperTypeOfVarType($innerType, $innerVarTagType); + return !$this->isSuperTypeOfVarType($scope, $innerType, $innerVarTagType); } - return $this->checkType($innerType, $innerVarTagType, $depth + 1); + return $this->checkType($scope, $innerType, $innerVarTagType, $depth + 1); } if ($depth === 0 && $type->isConstantValue()->yes()) { - return !$this->isAtLeastMaybeSuperTypeOfVarType($type, $varTagType); + return !$this->isAtLeastMaybeSuperTypeOfVarType($scope, $type, $varTagType); } - return !$this->isSuperTypeOfVarType($type, $varTagType); + return !$this->isSuperTypeOfVarType($scope, $type, $varTagType); } - private function isSuperTypeOfVarType(Type $type, Type $varTagType): bool + private function isSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType): bool { if ($type->isSuperTypeOf($varTagType)->yes()) { return true; } - $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, [])); + try { + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope)); + } catch (NameScopeAlreadyBeingCreatedException) { + return false; + } return $type->isSuperTypeOf($varTagType)->yes(); } - private function isAtLeastMaybeSuperTypeOfVarType(Type $type, Type $varTagType): bool + private function isAtLeastMaybeSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType): bool { if (!$type->isSuperTypeOf($varTagType)->no()) { return true; } - $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), new NameScope(null, [])); + try { + $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope)); + } catch (NameScopeAlreadyBeingCreatedException) { + return false; + } return !$type->isSuperTypeOf($varTagType)->no(); } + /** + * @throws NameScopeAlreadyBeingCreatedException + */ + private function createNameScope(Scope $scope): NameScope + { + $function = $scope->getFunction(); + + return $this->fileTypeMapper->getNameScope( + $scope->getFile(), + $scope->isInClass() ? $scope->getClassReflection()->getName() : null, + $scope->isInTrait() ? $scope->getTraitReflection()->getName() : null, + $function !== null ? $function->getName() : null, + ); + } + } diff --git a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php index 7a5d611a77..08bb43b2fd 100644 --- a/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php @@ -5,6 +5,7 @@ use PHPStan\PhpDoc\TypeNodeResolver; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; +use PHPStan\Type\FileTypeMapper; /** * @extends RuleTestCase @@ -16,6 +17,7 @@ protected function getRule(): Rule { return new VarTagChangedExpressionTypeRule(new VarTagTypeRuleHelper( self::getContainer()->getByType(TypeNodeResolver::class), + self::getContainer()->getByType(FileTypeMapper::class), true, true, )); diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 3380dca4da..c4bb1aec92 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -26,6 +26,7 @@ protected function getRule(): Rule self::getContainer()->getByType(FileTypeMapper::class), new VarTagTypeRuleHelper( self::getContainer()->getByType(TypeNodeResolver::class), + self::getContainer()->getByType(FileTypeMapper::class), $this->checkTypeAgainstPhpDocType, $this->strictWideningCheck, ), From e80620c83035b30e5ad6352a88ee4437361ec924 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 26 Feb 2025 14:52:10 +0100 Subject: [PATCH 7/8] Update test --- tests/PHPStan/Analyser/AnalyserIntegrationTest.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index a4ee70e41f..a73ff8ec72 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -1198,9 +1198,7 @@ public function testBug5091(): void public function testBug9459(): void { $errors = $this->runAnalyse(__DIR__ . '/data/bug-9459.php'); - $this->assertCount(1, $errors); - $this->assertSame('PHPDoc tag @var with type callable(): array is not subtype of native type Closure(): array{}.', $errors[0]->getMessage()); - $this->assertSame(10, $errors[0]->getLine()); + $this->assertCount(0, $errors); } public function testBug9573(): void From 210c0b347726568e9a6061808b25e1808b4b6fd7 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Wed, 26 Feb 2025 16:31:17 +0100 Subject: [PATCH 8/8] Silent error --- src/Rules/PhpDoc/VarTagTypeRuleHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index 0b1dfbc8ac..0c1d4e1b49 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -207,7 +207,7 @@ private function isSuperTypeOfVarType(Scope $scope, Type $type, Type $varTagType try { $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope)); } catch (NameScopeAlreadyBeingCreatedException) { - return false; + return true; } return $type->isSuperTypeOf($varTagType)->yes(); @@ -222,7 +222,7 @@ private function isAtLeastMaybeSuperTypeOfVarType(Scope $scope, Type $type, Type try { $type = $this->typeNodeResolver->resolve($type->toPhpDocNode(), $this->createNameScope($scope)); } catch (NameScopeAlreadyBeingCreatedException) { - return false; + return true; } return !$type->isSuperTypeOf($varTagType)->no();