Skip to content

Commit 02869f6

Browse files
committed
Add getScopeType and getScopeNativeType again
The `getType()` method along with FNSR enabled waits for the Expr analysis to be completed in order to evaluate the type at the right place in the code. This prevents tricky bugs when reasoning about code like `doFoo($a = 1, $a)`. Sometimes this is counter-productive because we actually want to use the current Scope object contents to resolve the Expr type. For example if we're interested in the true type of `$foo` in `isset($foo)`. In these cases use `getScopeType()` and `getScopeNativeType`.
1 parent 9a31328 commit 02869f6

10 files changed

Lines changed: 51 additions & 20 deletions

File tree

src/Analyser/Fiber/FiberScope.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,16 @@ public function getType(Expr $node): Type
6565
return $scope->getType($node);
6666
}
6767

68+
public function getScopeType(Expr $expr): Type
69+
{
70+
return $this->toMutatingScope()->getType($expr);
71+
}
72+
73+
public function getScopeNativeType(Expr $expr): Type
74+
{
75+
return $this->toMutatingScope()->getNativeType($expr);
76+
}
77+
6878
/** @api */
6979
public function getNativeType(Expr $expr): Type
7080
{

src/Analyser/MutatingScope.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,16 @@ public function getType(Expr $node): Type
877877
return $this->resolvedTypes[$key];
878878
}
879879

880+
public function getScopeType(Expr $expr): Type
881+
{
882+
return $this->getType($expr);
883+
}
884+
885+
public function getScopeNativeType(Expr $expr): Type
886+
{
887+
return $this->getNativeType($expr);
888+
}
889+
880890
private function getNodeKey(Expr $node): string
881891
{
882892
$key = $this->exprPrinter->printExpr($node);

src/Analyser/Scope.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,23 @@ public function getNativeType(Expr $expr): Type;
108108

109109
public function getKeepVoidType(Expr $node): Type;
110110

111+
/**
112+
* The `getType()` method along with FNSR enabled
113+
* waits for the Expr analysis to be completed
114+
* in order to evaluate the type at the right place in the code.
115+
*
116+
* This prevents tricky bugs when reasoning about code like
117+
* `doFoo($a = 1, $a)`.
118+
*
119+
* Sometimes this is counter-productive because we actually want
120+
* to use the current Scope object contents to resolve the Expr type.
121+
*
122+
* In these cases use `getScopeType()`.
123+
*/
124+
public function getScopeType(Expr $expr): Type;
125+
126+
public function getScopeNativeType(Expr $expr): Type;
127+
111128
public function resolveName(Name $name): string;
112129

113130
public function resolveTypeByName(Name $name): TypeWithClassName;

src/Rules/IssetCheck.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str
5454
return null;
5555
}
5656

57-
$type = $this->treatPhpDocTypesAsCertain ? $scope->getType($expr) : $scope->getNativeType($expr);
57+
$type = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr) : $scope->getScopeNativeType($expr);
5858
if (!$type instanceof NeverType) {
5959
return $this->generateError(
6060
$type,
@@ -74,15 +74,15 @@ public function check(Expr $expr, Scope $scope, string $operatorDescription, str
7474
return $error;
7575
} elseif ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) {
7676
$type = $this->treatPhpDocTypesAsCertain
77-
? $scope->getType($expr->var)
78-
: $scope->getNativeType($expr->var);
77+
? $scope->getScopeType($expr->var)
78+
: $scope->getScopeNativeType($expr->var);
7979
if (!$type->isOffsetAccessible()->yes()) {
8080
return $error ?? $this->checkUndefined($expr->var, $scope, $operatorDescription, $identifier);
8181
}
8282

8383
$dimType = $this->treatPhpDocTypesAsCertain
84-
? $scope->getType($expr->dim)
85-
: $scope->getNativeType($expr->dim);
84+
? $scope->getScopeType($expr->dim)
85+
: $scope->getScopeNativeType($expr->dim);
8686
$hasOffsetValue = $type->hasOffsetValueType($dimType);
8787
if ($hasOffsetValue->no()) {
8888
if (!$this->checkAdvancedIsset) {
@@ -241,7 +241,7 @@ static function (Type $type) use ($typeMessageCallback): ?string {
241241
}
242242

243243
$error = $this->generateError(
244-
$this->treatPhpDocTypesAsCertain ? $scope->getType($expr) : $scope->getNativeType($expr),
244+
$this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr) : $scope->getScopeNativeType($expr),
245245
sprintf('Expression %s', $operatorDescription),
246246
$typeMessageCallback,
247247
$identifier,
@@ -283,8 +283,8 @@ private function checkUndefined(Expr $expr, Scope $scope, string $operatorDescri
283283
}
284284

285285
if ($expr instanceof Node\Expr\ArrayDimFetch && $expr->dim !== null) {
286-
$type = $this->treatPhpDocTypesAsCertain ? $scope->getType($expr->var) : $scope->getNativeType($expr->var);
287-
$dimType = $this->treatPhpDocTypesAsCertain ? $scope->getType($expr->dim) : $scope->getNativeType($expr->dim);
286+
$type = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr->var) : $scope->getScopeNativeType($expr->var);
287+
$dimType = $this->treatPhpDocTypesAsCertain ? $scope->getScopeType($expr->dim) : $scope->getScopeNativeType($expr->dim);
288288
$hasOffsetValue = $type->hasOffsetValueType($dimType);
289289
if (!$type->isOffsetAccessible()->yes()) {
290290
return $this->checkUndefined($expr->var, $scope, $operatorDescription, $identifier);

src/Rules/Methods/NullsafeMethodCallRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public function getNodeType(): string
2424

2525
public function processNode(Node $node, Scope $scope): array
2626
{
27-
$calledOnType = $scope->getType($node->var);
27+
$calledOnType = $scope->getScopeType($node->var);
2828
if (!$calledOnType->isNull()->no()) {
2929
return [];
3030
}

src/Rules/PhpDoc/VarTagTypeRuleHelper.php

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use PHPStan\Analyser\Scope;
99
use PHPStan\DependencyInjection\AutowiredParameter;
1010
use PHPStan\DependencyInjection\AutowiredService;
11-
use PHPStan\Node\DeepNodeCloner;
1211
use PHPStan\Node\Expr\GetOffsetValueTypeExpr;
1312
use PHPStan\PhpDoc\NameScopeAlreadyBeingCreatedException;
1413
use PHPStan\PhpDoc\Tag\VarTag;
@@ -37,7 +36,6 @@ public function __construct(
3736
private TypeNodeResolver $typeNodeResolver,
3837
private FileTypeMapper $fileTypeMapper,
3938
private ReflectionProvider $reflectionProvider,
40-
private DeepNodeCloner $deepNodeCloner,
4139
#[AutowiredParameter(ref: '%reportWrongPhpDocTypeInVarTag%')]
4240
private bool $checkTypeAgainstPhpDocType,
4341
#[AutowiredParameter(ref: '%reportAnyTypeWideningInVarTag%')]
@@ -92,7 +90,7 @@ public function checkVarType(Scope $scope, Node\Expr $var, Node\Expr $expr, arra
9290
public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): array
9391
{
9492
$errors = [];
95-
$exprNativeType = $scope->getNativeType($this->deepNodeCloner->cloneNode($expr));
93+
$exprNativeType = $scope->getScopeNativeType($expr);
9694
$containsPhpStanType = $this->containsPhpStanType($varTagType);
9795
if ($this->shouldVarTagTypeBeReported($scope, $expr, $exprNativeType, $varTagType)) {
9896
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType);
@@ -102,7 +100,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
102100
$exprNativeType->describe($verbosity),
103101
))->identifier('varTag.nativeType')->build();
104102
} else {
105-
$exprType = $scope->getType($this->deepNodeCloner->cloneNode($expr));
103+
$exprType = $scope->getScopeType($expr);
106104
if (
107105
$this->shouldVarTagTypeBeReported($scope, $expr, $exprType, $varTagType)
108106
&& ($this->checkTypeAgainstPhpDocType || $containsPhpStanType)
@@ -117,7 +115,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
117115
}
118116

119117
if (count($errors) === 0 && $containsPhpStanType) {
120-
$exprType = $scope->getType($this->deepNodeCloner->cloneNode($expr));
118+
$exprType = $scope->getScopeType($expr);
121119
if (!$exprType->equals($varTagType)) {
122120
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType);
123121
$errors[] = RuleErrorBuilder::message(sprintf(

src/Rules/Properties/NullsafePropertyFetchRule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public function getNodeType(): string
2828

2929
public function processNode(Node $node, Scope $scope): array
3030
{
31-
$calledOnType = $scope->getType($node->var);
31+
$calledOnType = $scope->getScopeType($node->var);
3232
if (!$calledOnType->isNull()->no()) {
3333
return [];
3434
}

tests/PHPStan/Rules/PhpDoc/VarTagChangedExpressionTypeRuleTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PHPStan\Rules\PhpDoc;
44

5-
use PHPStan\Node\DeepNodeCloner;
65
use PHPStan\PhpDoc\TypeNodeResolver;
76
use PHPStan\Rules\Rule;
87
use PHPStan\Testing\RuleTestCase;
@@ -21,7 +20,6 @@ protected function getRule(): Rule
2120
$container->getByType(TypeNodeResolver::class),
2221
$container->getByType(FileTypeMapper::class),
2322
self::createReflectionProvider(),
24-
$container->getByType(DeepNodeCloner::class),
2523
true,
2624
true,
2725
));

tests/PHPStan/Rules/PhpDoc/WrongVariableNameInVarTagRuleTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace PHPStan\Rules\PhpDoc;
44

5-
use PHPStan\Node\DeepNodeCloner;
65
use PHPStan\PhpDoc\TypeNodeResolver;
76
use PHPStan\Rules\Rule;
87
use PHPStan\Testing\RuleTestCase;
@@ -29,7 +28,6 @@ protected function getRule(): Rule
2928
$container->getByType(TypeNodeResolver::class),
3029
$container->getByType(FileTypeMapper::class),
3130
self::createReflectionProvider(),
32-
$container->getByType(DeepNodeCloner::class),
3331
$this->checkTypeAgainstPhpDocType,
3432
$this->strictWideningCheck,
3533
),

tests/PHPStan/Rules/Variables/Bug13983Rule.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public function getNodeType(): string
2525
*/
2626
public function processNode(Node $node, Scope $scope): array
2727
{
28-
$type = $scope->getType($node->vars[0]);
28+
$type = $scope->getScopeType($node->vars[0]);
2929
$error = RuleErrorBuilder::message('Dumped: ' . $type->describe(VerbosityLevel::precise()))
3030
->identifier('dump.isset')
3131
->build();

0 commit comments

Comments
 (0)