diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 742906195a..5f08db26ad 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -813,6 +813,11 @@ parameters: count: 2 path: src/Type/Constant/ConstantArrayTypeBuilder.php + - + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType\\|PHPStan\\\\Type\\\\Constant\\\\ConstantStringType but it's error\\-prone and dangerous\\.$#" + count: 2 + path: src/Type/Constant/ConstantArrayTypeBuilder.php + - message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#" count: 2 @@ -1448,6 +1453,11 @@ parameters: count: 1 path: src/Type/StaticType.php + - + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\StaticType but it's error\\-prone and dangerous\\.$#" + count: 1 + path: src/Type/StaticType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#" count: 1 @@ -1583,6 +1593,11 @@ parameters: count: 1 path: src/Type/UnionType.php + - + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\BooleanType but it's error\\-prone and dangerous\\.$#" + count: 1 + path: src/Type/UnionType.php + - message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#" count: 3 @@ -1710,3 +1725,8 @@ parameters: count: 1 path: tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php + - + message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Generic\\\\TemplateType is always PHPStan\\\\Type\\\\Generic\\\\TemplateMixedType but it's error\\-prone and dangerous\\.$#" + count: 1 + path: tests/PHPStan/Type/IterableTypeTest.php + diff --git a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php index bb02a0108f..869e06d5bd 100644 --- a/src/Rules/PhpDoc/VarTagTypeRuleHelper.php +++ b/src/Rules/PhpDoc/VarTagTypeRuleHelper.php @@ -12,6 +12,7 @@ use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; +use PHPStan\Type\TypeUtils; use PHPStan\Type\VerbosityLevel; use function array_key_exists; use function count; @@ -72,6 +73,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)) { $verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType); $errors[] = RuleErrorBuilder::message(sprintf( @@ -79,9 +81,12 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): $varTagType->describe($verbosity), $exprNativeType->describe($verbosity), ))->build(); - } elseif ($this->checkTypeAgainstPhpDocType) { + } else { $exprType = $scope->getType($expr); - if ($this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType)) { + if ( + $this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType) + && ($this->checkTypeAgainstPhpDocType || $containsPhpStanType) + ) { $verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType); $errors[] = RuleErrorBuilder::message(sprintf( 'PHPDoc tag @var with type %s is not subtype of type %s.', @@ -91,9 +96,35 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): } } + if (count($errors) === 0 && $containsPhpStanType) { + $exprType = $scope->getType($expr); + if (!$exprType->equals($varTagType)) { + $verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType); + $errors[] = RuleErrorBuilder::message(sprintf( + 'PHPDoc tag @var assumes the expression with type %s is always %s but it\'s error-prone and dangerous.', + $exprType->describe($verbosity), + $varTagType->describe($verbosity), + ))->build(); + } + } + return $errors; } + private function containsPhpStanType(Type $type): bool + { + $classReflections = TypeUtils::toBenevolentUnion($type)->getObjectClassReflections(); + foreach ($classReflections as $classReflection) { + if (!$classReflection->isSubclassOf(Type::class)) { + continue; + } + + return true; + } + + return false; + } + private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $varTagType): bool { if ($expr instanceof Expr\New_) { diff --git a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php index 7fefce14d6..4187a25937 100644 --- a/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php +++ b/tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php @@ -226,6 +226,30 @@ public function dataReportWrongType(): iterable 'PHPDoc tag @var with type int is not subtype of native type \'foo\'.', 148, ], + [ + 'PHPDoc tag @var with type stdClass is not subtype of native type PHPStan\Type\Type|null.', + 186, + ], + [ + 'PHPDoc tag @var assumes the expression with type PHPStan\Type\Type|null is always PHPStan\Type\ObjectType|null but it\'s error-prone and dangerous.', + 189, + ], + [ + 'PHPDoc tag @var assumes the expression with type PHPStan\Type\Type|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.', + 192, + ], + [ + 'PHPDoc tag @var assumes the expression with type PHPStan\Type\ObjectType|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.', + 195, + ], + [ + 'PHPDoc tag @var with type PHPStan\Type\Type|null is not subtype of native type PHPStan\Type\ObjectType|null.', + 201, + ], + [ + 'PHPDoc tag @var with type PHPStan\Type\ObjectType|null is not subtype of type PHPStan\Type\Generic\GenericObjectType|null.', + 204, + ], ]]; yield [false, true, []]; yield [true, true, [ @@ -294,6 +318,30 @@ public function dataReportWrongType(): iterable 'PHPDoc tag @var with type array> is not subtype of type array>.', 163, ], + [ + 'PHPDoc tag @var with type stdClass is not subtype of native type PHPStan\Type\Type|null.', + 186, + ], + [ + 'PHPDoc tag @var assumes the expression with type PHPStan\Type\Type|null is always PHPStan\Type\ObjectType|null but it\'s error-prone and dangerous.', + 189, + ], + [ + 'PHPDoc tag @var assumes the expression with type PHPStan\Type\Type|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.', + 192, + ], + [ + 'PHPDoc tag @var assumes the expression with type PHPStan\Type\ObjectType|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.', + 195, + ], + [ + 'PHPDoc tag @var with type PHPStan\Type\Type|null is not subtype of native type PHPStan\Type\ObjectType|null.', + 201, + ], + [ + 'PHPDoc tag @var with type PHPStan\Type\ObjectType|null is not subtype of type PHPStan\Type\Generic\GenericObjectType|null.', + 204, + ], ]]; } diff --git a/tests/PHPStan/Rules/PhpDoc/data/wrong-var-native-type.php b/tests/PHPStan/Rules/PhpDoc/data/wrong-var-native-type.php index 55dcd5b026..67cfe231c1 100644 --- a/tests/PHPStan/Rules/PhpDoc/data/wrong-var-native-type.php +++ b/tests/PHPStan/Rules/PhpDoc/data/wrong-var-native-type.php @@ -170,3 +170,56 @@ private function arrayOfLists(): array } } + +class PHPStanType +{ + + public function doFoo(): void + { + /** @var \PHPStan\Type\Type $a */ + $a = $this->doBar(); // not narrowing - ok + + /** @var \PHPStan\Type\Type|null $b */ + $b = $this->doBar(); // not narrowing - ok + + /** @var \stdClass $c */ + $c = $this->doBar(); // not subtype - error + + /** @var \PHPStan\Type\ObjectType|null $d */ + $d = $this->doBar(); // narrowing Type - error + + /** @var \PHPStan\Type\ObjectType $e */ + $e = $this->doBar(); // narrowing Type - error + + /** @var \PHPStan\Type\ObjectType $f */ + $f = $this->doBaz(); // not narrowing - does not have to error but currently does + + /** @var \PHPStan\Type\ObjectType|null $g */ + $g = $this->doBaz(); // not narrowing - ok + + /** @var \PHPStan\Type\Type|null $g */ + $g = $this->doBaz(); // generalizing - not ok + + /** @var \PHPStan\Type\ObjectType|null $h */ + $h = $this->doBazPhpDoc(); // generalizing - not ok + } + + public function doBar(): ?\PHPStan\Type\Type + { + + } + + public function doBaz(): ?\PHPStan\Type\ObjectType + { + + } + + /** + * @return \PHPStan\Type\Generic\GenericObjectType|null + */ + public function doBazPhpDoc() + { + + } + +}