Skip to content

Commit d09f754

Browse files
committed
Add getScopeType and getScopeNativeType instead of cloning nodes
1 parent 0b4ab1e commit d09f754

File tree

8 files changed

+58
-35
lines changed

8 files changed

+58
-35
lines changed

src/Analyser/Fiber/FiberScope.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ public function getType(Expr $node): Type
4949
return $exprResult->getBeforeScope()->toMutatingScope()->getType($node);
5050
}
5151

52+
public function getScopeType(Expr $expr): Type
53+
{
54+
return $this->toMutatingScope()->getType($expr);
55+
}
56+
57+
public function getScopeNativeType(Expr $expr): Type
58+
{
59+
return $this->toMutatingScope()->getNativeType($expr);
60+
}
61+
5262
/** @api */
5363
public function getNativeType(Expr $expr): Type
5464
{

src/Analyser/MutatingScope.php

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

867+
public function getScopeType(Expr $expr): Type
868+
{
869+
return $this->getType($expr);
870+
}
871+
872+
public function getScopeNativeType(Expr $expr): Type
873+
{
874+
return $this->getNativeType($expr);
875+
}
876+
867877
private function getNodeKey(Expr $node): string
868878
{
869879
$key = $this->exprPrinter->printExpr($node);

src/Analyser/NodeScopeResolver.php

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3209,20 +3209,14 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
32093209
$impurePoints = array_merge($impurePoints, $result->getImpurePoints());
32103210
$isAlwaysTerminating = $isAlwaysTerminating || $result->isAlwaysTerminating();
32113211
} elseif ($expr instanceof Expr\NullsafeMethodCall) {
3212-
$this->processExprNode($stmt, new MethodCall(
3213-
$expr->var,
3214-
$expr->name,
3215-
$expr->args,
3216-
), $scope, $storage, new NoopNodeCallback(), $context);
3217-
32183212
$beforeScope = $scope;
32193213
$nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var);
32203214
$exprResult = $this->processExprNode(
32213215
$stmt,
32223216
new MethodCall(
3223-
$this->deepNodeCloner->cloneNode($expr->var),
3224-
$this->deepNodeCloner->cloneNode($expr->name),
3225-
array_map(fn ($node) => $this->deepNodeCloner->cloneNode($node), $expr->args),
3217+
$expr->var,
3218+
$expr->name,
3219+
$expr->args,
32263220
array_merge($expr->getAttributes(), ['virtualNullsafeMethodCall' => true]),
32273221
),
32283222
$nonNullabilityResult->getScope(),
@@ -3443,16 +3437,11 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
34433437
}
34443438
}
34453439
} elseif ($expr instanceof Expr\NullsafePropertyFetch) {
3446-
$this->processExprNode($stmt, new PropertyFetch(
3447-
$expr->var,
3448-
$expr->name,
3449-
), $scope, $storage, new NoopNodeCallback(), $context);
3450-
34513440
$beforeScope = $scope;
34523441
$nonNullabilityResult = $this->ensureShallowNonNullability($scope, $scope, $expr->var);
34533442
$exprResult = $this->processExprNode($stmt, new PropertyFetch(
3454-
$this->deepNodeCloner->cloneNode($expr->var),
3455-
$this->deepNodeCloner->cloneNode($expr->name),
3443+
$expr->var,
3444+
$expr->name,
34563445
array_merge($expr->getAttributes(), ['virtualNullsafePropertyFetch' => true]),
34573446
), $nonNullabilityResult->getScope(), $storage, $nodeCallback, $context);
34583447
$scope = $this->revertNonNullability($exprResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions());
@@ -3661,10 +3650,9 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
36613650
$this->storeResult($storage, $expr, $result);
36623651
return $result;
36633652
} elseif ($expr instanceof Coalesce) {
3664-
$this->processExprNode($stmt, $expr->left, $scope, $storage, new NoopNodeCallback(), $context->enterDeep());
36653653
$nonNullabilityResult = $this->ensureNonNullability($scope, $expr->left);
36663654
$condScope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->left);
3667-
$condResult = $this->processExprNode($stmt, $this->deepNodeCloner->cloneNode($expr->left), $condScope, $storage, $nodeCallback, $context->enterDeep());
3655+
$condResult = $this->processExprNode($stmt, $expr->left, $condScope, $storage, $nodeCallback, $context->enterDeep());
36683656
$scope = $this->revertNonNullability($condResult->getScope(), $nonNullabilityResult->getSpecifiedExpressions());
36693657
$scope = $this->lookForUnsetAllowedUndefinedExpressions($scope, $expr->left);
36703658

@@ -3859,10 +3847,9 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
38593847
$this->callNodeCallback($nodeCallback, $expr->name, $scope, $storage);
38603848
}
38613849
} elseif ($expr instanceof Expr\Empty_) {
3862-
$this->processExprNode($stmt, $expr->expr, $scope, $storage, new NoopNodeCallback(), $context->enterDeep());
38633850
$nonNullabilityResult = $this->ensureNonNullability($scope, $expr->expr);
38643851
$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $expr->expr);
3865-
$result = $this->processExprNode($stmt, $this->deepNodeCloner->cloneNode($expr->expr), $scope, $storage, $nodeCallback, $context->enterDeep());
3852+
$result = $this->processExprNode($stmt, $expr->expr, $scope, $storage, $nodeCallback, $context->enterDeep());
38663853
$scope = $result->getScope();
38673854
$hasYield = $result->hasYield();
38683855
$throwPoints = $result->getThrowPoints();
@@ -3877,10 +3864,9 @@ function (MutatingScope $scope) use ($stmt, $expr, $nodeCallback, $context, $sto
38773864
$nonNullabilityResults = [];
38783865
$isAlwaysTerminating = false;
38793866
foreach ($expr->vars as $var) {
3880-
$this->processExprNode($stmt, $var, $scope, $storage, new NoopNodeCallback(), $context->enterDeep());
38813867
$nonNullabilityResult = $this->ensureNonNullability($scope, $var);
38823868
$scope = $this->lookForSetAllowedUndefinedExpressions($nonNullabilityResult->getScope(), $var);
3883-
$result = $this->processExprNode($stmt, $this->deepNodeCloner->cloneNode($var), $scope, $storage, $nodeCallback, $context->enterDeep());
3869+
$result = $this->processExprNode($stmt, $var, $scope, $storage, $nodeCallback, $context->enterDeep());
38843870
$scope = $result->getScope();
38853871
$hasYield = $hasYield || $result->hasYield();
38863872
$throwPoints = array_merge($throwPoints, $result->getThrowPoints());

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 & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public function checkVarType(Scope $scope, Node\Expr $var, Node\Expr $expr, arra
9090
public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType): array
9191
{
9292
$errors = [];
93-
$exprNativeType = $scope->getNativeType($expr);
93+
$exprNativeType = $scope->getScopeNativeType($expr);
9494
$containsPhpStanType = $this->containsPhpStanType($varTagType);
9595
if ($this->shouldVarTagTypeBeReported($scope, $expr, $exprNativeType, $varTagType)) {
9696
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprNativeType, $varTagType);
@@ -100,7 +100,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
100100
$exprNativeType->describe($verbosity),
101101
))->identifier('varTag.nativeType')->build();
102102
} else {
103-
$exprType = $scope->getType($expr);
103+
$exprType = $scope->getScopeType($expr);
104104
if (
105105
$this->shouldVarTagTypeBeReported($scope, $expr, $exprType, $varTagType)
106106
&& ($this->checkTypeAgainstPhpDocType || $containsPhpStanType)
@@ -115,7 +115,7 @@ public function checkExprType(Scope $scope, Node\Expr $expr, Type $varTagType):
115115
}
116116

117117
if (count($errors) === 0 && $containsPhpStanType) {
118-
$exprType = $scope->getType($expr);
118+
$exprType = $scope->getScopeType($expr);
119119
if (!$exprType->equals($varTagType)) {
120120
$verbosity = VerbosityLevel::getRecommendedLevelByType($exprType, $varTagType);
121121
$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
}

0 commit comments

Comments
 (0)