Skip to content

Commit

Permalink
Report narrowing PHPStan\Type\Type interface via @var
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Mar 8, 2023
1 parent e32c9ef commit 713b98f
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 2 deletions.
20 changes: 20 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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

35 changes: 33 additions & 2 deletions src/Rules/PhpDoc/VarTagTypeRuleHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -72,16 +73,20 @@ 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(
'PHPDoc tag @var with type %s is not subtype of native type %s.',
$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.',
Expand All @@ -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_) {
Expand Down
48 changes: 48 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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, [
Expand Down Expand Up @@ -294,6 +318,30 @@ public function dataReportWrongType(): iterable
'PHPDoc tag @var with type array<Traversable<mixed, string>> is not subtype of type array<list<string|null>>.',
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,
],
]];
}

Expand Down
53 changes: 53 additions & 0 deletions tests/PHPStan/Rules/PhpDoc/data/wrong-var-native-type.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{

}

}

0 comments on commit 713b98f

Please sign in to comment.