psalm icon indicating copy to clipboard operation
psalm copied to clipboard

InvalidDocblock when passing object to a function with @psalm-assert-if-true

Open mgiuffrida opened this issue 9 months ago • 7 comments

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.)

mgiuffrida avatar Apr 03 '25 01:04 mgiuffrida

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

psalm-github-bot[bot] avatar Apr 03 '25 01:04 psalm-github-bot[bot]

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

mgiuffrida avatar Apr 03 '25 01:04 mgiuffrida

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

psalm-github-bot[bot] avatar Apr 03 '25 01:04 psalm-github-bot[bot]

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)

orklah avatar Apr 03 '25 16:04 orklah

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: @.***>

mgiuffrida avatar Apr 03 '25 17:04 mgiuffrida

Yeah, something like this can work: https://psalm.dev/r/9d379f2147

orklah avatar Apr 03 '25 18:04 orklah

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

psalm-github-bot[bot] avatar Apr 03 '25 18:04 psalm-github-bot[bot]