diff --git a/src/Parser/AssignOpTrait.php b/src/Parser/AssignOpTrait.php index 8c7510a1..442dbd3c 100644 --- a/src/Parser/AssignOpTrait.php +++ b/src/Parser/AssignOpTrait.php @@ -568,7 +568,7 @@ protected function parseAssignFinally( // Parent/interface/abstract declarations are not precise enough for a concrete typed object. $runtimeObjectAssignClass = $leftClass; } else { - $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`"); + $this->reassignInferredObjectVar($left, $var, $leftClass, $rightClass); } } else { $this->checkVarAssignExpr($left, $this->getVarType($var), Type::OBJECT); @@ -644,7 +644,7 @@ protected function parseAssignFinally( } elseif ($this->isInterface($rightClass) || $this->isAbstractClass($rightClass) || $this->isObjectClassStaticallyAssignableTo($leftClass, $rightClass)) { $runtimeObjectAssignClass = $leftClass; } else { - $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`"); + $this->reassignInferredObjectVar($left, $var, $leftClass, $rightClass); } } } @@ -733,6 +733,27 @@ protected function parseAssignFinally( return $var . ' = ' . $assignedExpr; } + /** + * PHP local variables carry no type declaration; the class tracked for + * `$var` was inferred from an earlier assignment. Re-assigning an unrelated + * concrete class in a mutually exclusive branch (for example if/else) is + * valid PHP, so widen the inferred local to a generic dynamic object rather + * than rejecting it. Parameters, native objects, and explicitly declared + * object types keep their strict re-assignment check. + */ + protected function reassignInferredObjectVar(Expr $left, string $var, string $leftClass, string $rightClass): void + { + $canWiden = !$this->hasArgument($var) + && !$this->isNativeObjectVar($var) + && isset($this->context->objects[$var]) + && !isset($this->context->declaredObjects[$var]); + if (!$canWiden) { + $this->fatalError($left, "Cannot re-assign typed object `\${$var}` from `{$leftClass}` to `{$rightClass}`"); + } + unset($this->context->objects[$var], $this->context->stableObjects[$var], $this->context->declaredObjects[$var]); + $this->addLocalVar($var, Type::OBJECT); + } + protected function parseAssignPropertyHook( Expr\PropertyFetch $left, Expr $right, diff --git a/tests/compiler/reflection/inferred-object-reassign.phpt b/tests/compiler/reflection/inferred-object-reassign.phpt new file mode 100644 index 00000000..a7e248f1 --- /dev/null +++ b/tests/compiler/reflection/inferred-object-reassign.phpt @@ -0,0 +1,29 @@ +--TEST-- +Re-assign an inferred local object to a different class across if/else +--FILE-- + +--EXPECT-- +ReflectionFunction +ReflectionMethod