InvalidDocblock when passing object to a function with @psalm-assert-if-true
Simplified repro: https://psalm.dev/r/d55f65b8e2
/** @psalm-trace $obj */
$obj = (object)['hasFoo' => true]; // Correctly traced as an object
/**
@param object{hasFoo: bool} $obj
@psalm-assert-if-true true $obj->hasFoo
@return bool
*/
function validateFoo(object $obj): bool {
return $obj->hasFoo;
}
validateFoo($obj); // "Variable obj is not an object so the assertion cannot be applied"
My actual use case is to array_filter() an array of objects, returning the objects with two specific fields set, but I was able to reduce the Psalm problem down to the above. (The actual PHP runs as expected.)
I found these snippets:
https://psalm.dev/r/d55f65b8e2
<?php
/** @psalm-trace $obj */
$obj = (object)['hasFoo' => true];
/**
@param object{hasFoo: bool} $obj
@psalm-assert-if-true true $obj->hasFoo
@return bool
*/
function validateFoo(object $obj): bool {
return $obj->hasFoo;
}
validateFoo($obj);
Psalm output (using commit 1288d7a):
INFO: Trace - 4:1 - $obj: object{hasFoo:true}
ERROR: InvalidDocblock - 15:1 - Variable obj is not an object so the assertion cannot be applied
I don't know if this is related, but similar code triggers an internal Psalm error:
https://psalm.dev/r/688b0e0e5a
Internal Psalm error on line 4230 - /vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php
I found these snippets:
https://psalm.dev/r/688b0e0e5a
<?php
$arr = [(object)['hasFoo' => true], (object)['hasFoo' => false]];
/**
@param object{hasFoo: bool} $obj
@psalm-assert-if-true true $obj->hasFoo
@return bool
*/
function validateFoo(object $obj): bool {
return $obj->hasFoo;
}
validateFoo($arr[0]);
Psalm encountered an internal error:
/vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php: Psalm\Internal\Analyzer\Statements\Expression\AssertionFinder::isPropertyImmutableOnArgument(): Argument #4 ($arg_expr) must be of type PhpParser\Node\Expr\Variable, PhpParser\Node\Expr\ArrayDimFetch given, called in /vendor/vimeo/psalm/src/Psalm/Internal/Analyzer/Statements/Expression/AssertionFinder.php on line 1064
The first issue is something that is not supported yet (Psalm only asserts properties on named objects right now), the second issue is a bug (although once the bug is resolved, Psalm also doesn't support assertions on parts of an array)
Ok, thanks!
Could you please clarify what you mean about "named" objects? Is that as opposed to "instances of anonymous classes"?
(Would the more idiomatic solution be to define a class here?)
On Thu, Apr 3, 2025, 11:17 AM orklah @.***> wrote:
The first issue is something that is not supported yet (Psalm only asserts properties on named objects right now), the second issue is a bug (although once the bug is resolved, Psalm also doesn't support assertions on parts of an array)
— Reply to this email directly, view it on GitHub https://github.com/vimeo/psalm/issues/11386#issuecomment-2776314564, or unsubscribe https://github.com/notifications/unsubscribe-auth/AALB3GFQLUEAEKJ34IA24YT2XVNILAVCNFSM6AAAAAB2K5UBNKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONZWGMYTINJWGQ . You are receiving this because you authored the thread.Message ID: @.***> [image: orklah]orklah left a comment (vimeo/psalm#11386) https://github.com/vimeo/psalm/issues/11386#issuecomment-2776314564
The first issue is something that is not supported yet (Psalm only asserts properties on named objects right now), the second issue is a bug (although once the bug is resolved, Psalm also doesn't support assertions on parts of an array)
— Reply to this email directly, view it on GitHub https://github.com/vimeo/psalm/issues/11386#issuecomment-2776314564, or unsubscribe https://github.com/notifications/unsubscribe-auth/AALB3GFQLUEAEKJ34IA24YT2XVNILAVCNFSM6AAAAAB2K5UBNKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDONZWGMYTINJWGQ . You are receiving this because you authored the thread.Message ID: @.***>
Yeah, something like this can work: https://psalm.dev/r/9d379f2147
I found these snippets:
https://psalm.dev/r/9d379f2147
<?php
class A{
public bool $hasFoo;
}
$obj = new A();
/**
@param object{hasFoo: bool} $obj
@psalm-assert-if-true true $obj->hasFoo
@return bool
*/
function validateFoo(object $obj): bool {
return $obj->hasFoo;
}
if(validateFoo($obj)){
/** @psalm-trace $obj */;
/** @psalm-trace $obj->hasFoo */;
} else{
/** @psalm-trace $obj */;
/** @psalm-trace $obj->hasFoo */;
}
Psalm output (using commit 1288d7a):
INFO: Trace - 18:29 - $obj: A
INFO: Trace - 19:37 - $obj->hasFoo: true
INFO: Trace - 22:29 - $obj: A
INFO: Trace - 23:37 - $obj->hasFoo: false
ERROR: MissingConstructor - 3:14 - A has an uninitialized property A::$hasFoo, but no constructor