diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index c0b7931e..1c177c9a 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -870,14 +870,16 @@ protected function wrapObjectPropertyAssignTypeCheck(NodeAbstract $left, Expr $r return $rightExpr; } - if ($rightType !== Type::VAR && $this->canAssignStaticTypeToObjectProperty($def, $rightType)) { + $rightClass = $this->detectClassOfExpr($right); + // "object" matches any class by storage type alone; keep the runtime is-a check. + $unknownObjectIntoTypedProperty = $rightType === Type::OBJECT && $def->class !== '' && $rightClass === ''; + if ($rightType !== Type::VAR && $this->canAssignStaticTypeToObjectProperty($def, $rightType) && !$unknownObjectIntoTypedProperty) { return $rightExpr; } if ($rightType === Type::VAR && ($helper = $this->getFixedPropertyTypeCheckHelper($def)) !== null) { return $helper . '(' . $rightExpr . ', ' . $this->genCharPtr($this->getObjectPropertyTypeCheckDisplayName($left), true) . ')'; } - $rightClass = $this->detectClassOfExpr($right); if ($rightClass !== '' && $compositeRelation === null) { return $rightExpr; } diff --git a/tests/compiler/object_property/object-param-unknown-class-assign.phpt b/tests/compiler/object_property/object-param-unknown-class-assign.phpt new file mode 100644 index 00000000..a2b82060 --- /dev/null +++ b/tests/compiler/object_property/object-param-unknown-class-assign.phpt @@ -0,0 +1,94 @@ +--TEST-- +object values with a statically unknown class keep the typed property is-a check +--FILE-- +p = $value; +} + +function assignNullableObject(object $value, ParamHolder $holder): void +{ + $holder->nullable = $value; +} + +function assignGenericObject(object $value, ParamHolder $holder): void +{ + $holder->generic = $value; +} + +function main() +{ + $holder = new ParamHolder(); + + try { + assignObject(new ParamTargetExpected(), $holder); + echo "valid assignment accepted\n"; + var_dump($holder->p->value()); + } catch (Throwable $error) { + echo $error::class, "\n"; + } + + try { + assignObject(new ParamTargetChild(), $holder); + echo "subclass assignment accepted\n"; + var_dump($holder->p->value()); + } catch (Throwable $error) { + echo $error::class, "\n"; + } + + try { + assignObject(new ParamTargetOther(), $holder); + echo "invalid assignment accepted\n"; + } catch (Throwable $error) { + echo $error::class, "\n"; + } + + try { + assignNullableObject(new ParamTargetOther(), $holder); + echo "invalid nullable assignment accepted\n"; + } catch (Throwable $error) { + echo $error::class, "\n"; + } + + try { + assignGenericObject(new ParamTargetOther(), $holder); + echo "generic object assignment accepted\n"; + var_dump($holder->generic::class); + } catch (Throwable $error) { + echo $error::class, "\n"; + } +} +?> +--EXPECT-- +valid assignment accepted +string(8) "expected" +subclass assignment accepted +string(8) "expected" +TypeError +TypeError +generic object assignment accepted +string(16) "ParamTargetOther"