Skip to content

Commit 713b98f

Browse files
committed
Report narrowing PHPStan\Type\Type interface via @var
1 parent e32c9ef commit 713b98f

File tree

4 files changed

+154
-2
lines changed

4 files changed

+154
-2
lines changed

phpstan-baseline.neon

+20
Original file line numberDiff line numberDiff line change
@@ -813,6 +813,11 @@ parameters:
813813
count: 2
814814
path: src/Type/Constant/ConstantArrayTypeBuilder.php
815815

816+
-
817+
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\\.$#"
818+
count: 2
819+
path: src/Type/Constant/ConstantArrayTypeBuilder.php
820+
816821
-
817822
message: "#^PHPDoc tag @var with type float\\|int is not subtype of native type int\\.$#"
818823
count: 2
@@ -1448,6 +1453,11 @@ parameters:
14481453
count: 1
14491454
path: src/Type/StaticType.php
14501455

1456+
-
1457+
message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\StaticType but it's error\\-prone and dangerous\\.$#"
1458+
count: 1
1459+
path: src/Type/StaticType.php
1460+
14511461
-
14521462
message: "#^Doing instanceof PHPStan\\\\Type\\\\Constant\\\\ConstantStringType is error\\-prone and deprecated\\. Use Type\\:\\:getConstantStrings\\(\\) instead\\.$#"
14531463
count: 1
@@ -1583,6 +1593,11 @@ parameters:
15831593
count: 1
15841594
path: src/Type/UnionType.php
15851595

1596+
-
1597+
message: "#^PHPDoc tag @var assumes the expression with type PHPStan\\\\Type\\\\Type is always PHPStan\\\\Type\\\\BooleanType but it's error\\-prone and dangerous\\.$#"
1598+
count: 1
1599+
path: src/Type/UnionType.php
1600+
15861601
-
15871602
message: "#^Doing instanceof PHPStan\\\\Type\\\\Accessory\\\\AccessoryType is error\\-prone and deprecated\\. Use methods on PHPStan\\\\Type\\\\Type instead\\.$#"
15881603
count: 3
@@ -1710,3 +1725,8 @@ parameters:
17101725
count: 1
17111726
path: tests/PHPStan/Rules/Arrays/AppendedArrayKeyTypeRuleTest.php
17121727

1728+
-
1729+
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\\.$#"
1730+
count: 1
1731+
path: tests/PHPStan/Type/IterableTypeTest.php
1732+

src/Rules/PhpDoc/VarTagTypeRuleHelper.php

+33-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPStan\Type\Generic\GenericObjectType;
1313
use PHPStan\Type\ObjectType;
1414
use PHPStan\Type\Type;
15+
use PHPStan\Type\TypeUtils;
1516
use PHPStan\Type\VerbosityLevel;
1617
use function array_key_exists;
1718
use function count;
@@ -72,16 +73,20 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
7273
{
7374
$errors = [];
7475
$exprNativeType = $scope->getNativeType($expr);
76+
$containsPhpStanType = $this->containsPhpStanType($varTagType);
7577
if ($this->shouldVarTagTypeBeReported($expr, $exprNativeType, $varTagType)) {
7678
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType);
7779
$errors[] = RuleErrorBuilder::message(sprintf(
7880
'PHPDoc tag @var with type %s is not subtype of native type %s.',
7981
$varTagType->describe($verbosity),
8082
$exprNativeType->describe($verbosity),
8183
))->build();
82-
} elseif ($this->checkTypeAgainstPhpDocType) {
84+
} else {
8385
$exprType = $scope->getType($expr);
84-
if ($this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType)) {
86+
if (
87+
$this->shouldVarTagTypeBeReported($expr, $exprType, $varTagType)
88+
&& ($this->checkTypeAgainstPhpDocType || $containsPhpStanType)
89+
) {
8590
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType);
8691
$errors[] = RuleErrorBuilder::message(sprintf(
8792
'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):
9196
}
9297
}
9398

99+
if (count($errors) === 0 && $containsPhpStanType) {
100+
$exprType = $scope->getType($expr);
101+
if (!$exprType->equals($varTagType)) {
102+
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType);
103+
$errors[] = RuleErrorBuilder::message(sprintf(
104+
'PHPDoc tag @var assumes the expression with type %s is always %s but it\'s error-prone and dangerous.',
105+
$exprType->describe($verbosity),
106+
$varTagType->describe($verbosity),
107+
))->build();
108+
}
109+
}
110+
94111
return $errors;
95112
}
96113

114+
private function containsPhpStanType(Type $type): bool
115+
{
116+
$classReflections = TypeUtils::toBenevolentUnion($type)->getObjectClassReflections();
117+
foreach ($classReflections as $classReflection) {
118+
if (!$classReflection->isSubclassOf(Type::class)) {
119+
continue;
120+
}
121+
122+
return true;
123+
}
124+
125+
return false;
126+
}
127+
97128
private function shouldVarTagTypeBeReported(Node\Expr $expr, Type $type, Type $varTagType): bool
98129
{
99130
if ($expr instanceof Expr\New_) {

tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php

+48
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,30 @@ public function dataReportWrongType(): iterable
226226
'PHPDoc tag @var with type int is not subtype of native type \'foo\'.',
227227
148,
228228
],
229+
[
230+
'PHPDoc tag @var with type stdClass is not subtype of native type PHPStan\Type\Type|null.',
231+
186,
232+
],
233+
[
234+
'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.',
235+
189,
236+
],
237+
[
238+
'PHPDoc tag @var assumes the expression with type PHPStan\Type\Type|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.',
239+
192,
240+
],
241+
[
242+
'PHPDoc tag @var assumes the expression with type PHPStan\Type\ObjectType|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.',
243+
195,
244+
],
245+
[
246+
'PHPDoc tag @var with type PHPStan\Type\Type|null is not subtype of native type PHPStan\Type\ObjectType|null.',
247+
201,
248+
],
249+
[
250+
'PHPDoc tag @var with type PHPStan\Type\ObjectType|null is not subtype of type PHPStan\Type\Generic\GenericObjectType|null.',
251+
204,
252+
],
229253
]];
230254
yield [false, true, []];
231255
yield [true, true, [
@@ -294,6 +318,30 @@ public function dataReportWrongType(): iterable
294318
'PHPDoc tag @var with type array<Traversable<mixed, string>> is not subtype of type array<list<string|null>>.',
295319
163,
296320
],
321+
[
322+
'PHPDoc tag @var with type stdClass is not subtype of native type PHPStan\Type\Type|null.',
323+
186,
324+
],
325+
[
326+
'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.',
327+
189,
328+
],
329+
[
330+
'PHPDoc tag @var assumes the expression with type PHPStan\Type\Type|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.',
331+
192,
332+
],
333+
[
334+
'PHPDoc tag @var assumes the expression with type PHPStan\Type\ObjectType|null is always PHPStan\Type\ObjectType but it\'s error-prone and dangerous.',
335+
195,
336+
],
337+
[
338+
'PHPDoc tag @var with type PHPStan\Type\Type|null is not subtype of native type PHPStan\Type\ObjectType|null.',
339+
201,
340+
],
341+
[
342+
'PHPDoc tag @var with type PHPStan\Type\ObjectType|null is not subtype of type PHPStan\Type\Generic\GenericObjectType|null.',
343+
204,
344+
],
297345
]];
298346
}
299347

tests/PHPStan/Rules/PhpDoc/data/wrong-var-native-type.php

+53
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,56 @@ private function arrayOfLists(): array
170170
}
171171

172172
}
173+
174+
class PHPStanType
175+
{
176+
177+
public function doFoo(): void
178+
{
179+
/** @var \PHPStan\Type\Type $a */
180+
$a = $this->doBar(); // not narrowing - ok
181+
182+
/** @var \PHPStan\Type\Type|null $b */
183+
$b = $this->doBar(); // not narrowing - ok
184+
185+
/** @var \stdClass $c */
186+
$c = $this->doBar(); // not subtype - error
187+
188+
/** @var \PHPStan\Type\ObjectType|null $d */
189+
$d = $this->doBar(); // narrowing Type - error
190+
191+
/** @var \PHPStan\Type\ObjectType $e */
192+
$e = $this->doBar(); // narrowing Type - error
193+
194+
/** @var \PHPStan\Type\ObjectType $f */
195+
$f = $this->doBaz(); // not narrowing - does not have to error but currently does
196+
197+
/** @var \PHPStan\Type\ObjectType|null $g */
198+
$g = $this->doBaz(); // not narrowing - ok
199+
200+
/** @var \PHPStan\Type\Type|null $g */
201+
$g = $this->doBaz(); // generalizing - not ok
202+
203+
/** @var \PHPStan\Type\ObjectType|null $h */
204+
$h = $this->doBazPhpDoc(); // generalizing - not ok
205+
}
206+
207+
public function doBar(): ?\PHPStan\Type\Type
208+
{
209+
210+
}
211+
212+
public function doBaz(): ?\PHPStan\Type\ObjectType
213+
{
214+
215+
}
216+
217+
/**
218+
* @return \PHPStan\Type\Generic\GenericObjectType|null
219+
*/
220+
public function doBazPhpDoc()
221+
{
222+
223+
}
224+
225+
}

0 commit comments

Comments
 (0)