Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Parser/PropertyAccessTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
--TEST--
object values with a statically unknown class keep the typed property is-a check
--FILE--
<?php
class ParamTargetExpected
{
public function value(): string
{
return 'expected';
}
}

class ParamTargetChild extends ParamTargetExpected
{
}

class ParamTargetOther
{
}

class ParamHolder
{
public ParamTargetExpected $p;
public ?ParamTargetExpected $nullable;
public object $generic;
}

function assignObject(object $value, ParamHolder $holder): void
{
$holder->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"