diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 9eeb9e6bec..f4291a1c58 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -10,6 +10,7 @@ use PhpParser\Node\ComplexType; use PhpParser\Node\Expr; use PhpParser\Node\Expr\Array_; +use PhpParser\Node\Expr\ArrayDimFetch; use PhpParser\Node\Expr\BinaryOp; use PhpParser\Node\Expr\Cast\Unset_; use PhpParser\Node\Expr\ConstFetch; @@ -797,6 +798,21 @@ public function getAnonymousFunctionReturnType(): ?Type /** @api */ public function getType(Expr $node): Type { + if ($node instanceof Node\Scalar\Int_) { + return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); + } elseif ($node instanceof String_) { + return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); + } elseif ($node instanceof Node\Scalar\Float_) { + return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); + } elseif ($node instanceof Expr\UnaryMinus && $node->expr instanceof Node\Scalar) { + return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); + } elseif ($node instanceof ConstFetch) { + $loweredConstName = strtolower($node->name->toString()); + if (in_array($loweredConstName, ['true', 'false', 'null'], true)) { + return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); + } + } + if ($node instanceof GetIterableKeyTypeExpr) { return $this->getIterableKeyType($this->getType($node->getExpr())); } @@ -1295,11 +1311,7 @@ private function resolveType(string $exprString, Expr $node): Type }); } - if ($node instanceof Node\Scalar\Int_) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof String_) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); - } elseif ($node instanceof Node\Scalar\InterpolatedString) { + if ($node instanceof Node\Scalar\InterpolatedString) { $resultType = null; foreach ($node->parts as $part) { if ($part instanceof InterpolatedStringPart) { @@ -1316,8 +1328,6 @@ private function resolveType(string $exprString, Expr $node): Type } return $resultType ?? new ConstantStringType(''); - } elseif ($node instanceof Node\Scalar\Float_) { - return $this->initializerExprTypeResolver->getType($node, InitializerExprContext::fromScope($this)); } elseif ($node instanceof Expr\CallLike && $node->isFirstClassCallable()) { if ($node instanceof FuncCall && $node->name instanceof Expr) { $callableType = $this->getType($node->name); @@ -2039,16 +2049,6 @@ static function (Node $node, Scope $scope) use ($arrowScope, &$arrowFunctionImpu } if ($node instanceof ConstFetch) { - $constName = (string) $node->name; - $loweredConstName = strtolower($constName); - if ($loweredConstName === 'true') { - return new ConstantBooleanType(true); - } elseif ($loweredConstName === 'false') { - return new ConstantBooleanType(false); - } elseif ($loweredConstName === 'null') { - return new NullType(); - } - $namespacedName = null; if (!$node->name->isFullyQualified() && $this->getNamespace() !== null) { $namespacedName = new FullyQualified([$this->getNamespace(), $node->name->toString()]); @@ -4358,6 +4358,7 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, $this->parentScope, $this->nativeTypesPromoted, ); + $scope->resolvedTypes = $this->preserveResolvedTypes([$exprString => $expr]); if ($expr instanceof AlwaysRememberedExpr) { return $scope->specifyExpressionType($expr->expr, $type, $nativeType, $certainty); @@ -4406,6 +4407,35 @@ public function assignInitializedProperty(Type $fetchedOnType, string $propertyN return $this->assignExpression(new PropertyInitializationExpr($propertyName), new MixedType(), new MixedType()); } + /** + * @param array $changedExpressions + * + * @return array + */ + private function preserveResolvedTypes(array $changedExpressions): array + { + $preservedTypes = $this->resolvedTypes; + foreach($preservedTypes as $exprString => $exprTypeHolder) { + $exprExpr = $exprTypeHolder->getExpr(); + + foreach ($changedExpressions as $exprStringToInvalidate => $expressionToInvalidate) { + while ($expressionToInvalidate instanceof Expr\ArrayDimFetch) { + $expressionToInvalidate = $expressionToInvalidate->var; + $exprStringToInvalidate = $this->getNodeKey($expressionToInvalidate); + } + + if (!$this->shouldInvalidateExpression($exprStringToInvalidate, $expressionToInvalidate, $exprExpr)) { + continue; + } + + unset($preservedTypes[$exprString]); + continue 2; + } + } + + return $preservedTypes; + } + public function invalidateExpression(Expr $expressionToInvalidate, bool $requireMoreCharacters = false): self { $expressionTypes = $this->expressionTypes;