psalm icon indicating copy to clipboard operation
psalm copied to clipboard

Psalm don't detect inherit property type

Open vjik opened this issue 10 months ago • 13 comments

https://psalm.dev/r/b667f563eb

vjik avatar Feb 19 '25 13:02 vjik

I found these snippets:

https://psalm.dev/r/b667f563eb
<?php

class A
{
    /**
     * @var non-empty-string
     */
    public string $var = 'test';
}

class B extends A {
    public string $var = 'hello';
}
Psalm output (using commit a9e6444):

ERROR: NonInvariantDocblockPropertyType - 12:19 - Property B::$var has type string, not invariant with A::$var of type non-empty-string

psalm-github-bot[bot] avatar Feb 19 '25 13:02 psalm-github-bot[bot]

This is correct, overridden properties must not be looser than the parent (it's an extension of the equivalent rule present in PHP, applied to docblock types as well).

danog avatar Feb 19 '25 14:02 danog

This is correct, overridden properties must not be looser than the parent (it's an extension of the equivalent rule present in PHP, applied to docblock types as well).

But for method parameters type inherits correctly: https://psalm.dev/r/5afbcd2ec8

vjik avatar Feb 19 '25 14:02 vjik

I found these snippets:

https://psalm.dev/r/5afbcd2ec8
<?php

class A
{
    /**
     * @var non-empty-string
     */
    public string $var = 'test';
    
    /**
     * @param non-empty-string $var
     */
    public function test(string $var): void {
        echo $var;
    }
}

final class B extends A {
    public string $var = 'hello';
    
    public function test(string $var): void {
        /** @psalm-trace $var */
        echo $var;
    }
}
Psalm output (using commit a9e6444):

ERROR: NonInvariantDocblockPropertyType - 19:19 - Property B::$var has type string, not invariant with A::$var of type non-empty-string

INFO: Trace - 23:9 - $var: non-empty-string

psalm-github-bot[bot] avatar Feb 19 '25 14:02 psalm-github-bot[bot]

Method parameters can be looser, per LSP contravariance rules.

danog avatar Feb 19 '25 14:02 danog

"overridden properties must not be looser than the parent" - i agree, but see https://psalm.dev/r/b667f563eb :

Property A::$var is non-empty-string. Why Psalm think that B::$var is string? Shouldn't he understand that B::$var also is non-empty-string?

Why does it understand this in the parameters of the method (https://psalm.dev/r/5afbcd2ec8), but not in the properties of the class?

vjik avatar Feb 19 '25 19:02 vjik

I found these snippets:

https://psalm.dev/r/b667f563eb
<?php

class A
{
    /**
     * @var non-empty-string
     */
    public string $var = 'test';
}

class B extends A {
    public string $var = 'hello';
}
Psalm output (using commit a9e6444):

ERROR: NonInvariantDocblockPropertyType - 12:19 - Property B::$var has type string, not invariant with A::$var of type non-empty-string
https://psalm.dev/r/5afbcd2ec8
<?php

class A
{
    /**
     * @var non-empty-string
     */
    public string $var = 'test';
    
    /**
     * @param non-empty-string $var
     */
    public function test(string $var): void {
        echo $var;
    }
}

final class B extends A {
    public string $var = 'hello';
    
    public function test(string $var): void {
        /** @psalm-trace $var */
        echo $var;
    }
}
Psalm output (using commit a9e6444):

ERROR: NonInvariantDocblockPropertyType - 19:19 - Property B::$var has type string, not invariant with A::$var of type non-empty-string

INFO: Trace - 23:9 - $var: non-empty-string

psalm-github-bot[bot] avatar Feb 19 '25 19:02 psalm-github-bot[bot]

@danog WDYT?

vjik avatar Feb 23 '25 17:02 vjik

Why does it understand this in the parameters of the method (https://psalm.dev/r/5afbcd2ec8), but not in the properties of the class?

It doesn't, because loosening types in method parameters is simply allowed.

danog avatar Feb 23 '25 19:02 danog

I found these snippets:

https://psalm.dev/r/5afbcd2ec8
<?php

class A
{
    /**
     * @var non-empty-string
     */
    public string $var = 'test';
    
    /**
     * @param non-empty-string $var
     */
    public function test(string $var): void {
        echo $var;
    }
}

final class B extends A {
    public string $var = 'hello';
    
    public function test(string $var): void {
        /** @psalm-trace $var */
        echo $var;
    }
}
Psalm output (using commit be0f41a):

ERROR: NonInvariantDocblockPropertyType - 19:19 - Property B::$var has type string, not invariant with A::$var of type non-empty-string

INFO: Trace - 23:9 - $var: non-empty-string

psalm-github-bot[bot] avatar Feb 23 '25 19:02 psalm-github-bot[bot]

@danog I'll try to explain my pain again =)

Case with method parameter

class A
{    
    /**
     * @param non-empty-string $var
     */
    public function test(string $var): void {
        echo $var;
    }
}

final class B extends A {
    public function test(string $var): void {
        /** @psalm-trace $var */
        echo $var;
    }
}

Psalm without extra annotations in class B knows, that $var is non-empty-string. Not string. It's fine.

Case with class property

class A
{
    /**
     * @var non-empty-string
     */
    public string $var = 'test';
}

final class B extends A {
    public string $var = 'hello';
}

Psalm don't understand that B::$var is non-empty-string. Why?

vjik avatar Mar 05 '25 14:03 vjik

Okay I see your point now, will fix

danog avatar Mar 05 '25 16:03 danog

Any news?

vjik avatar Jul 25 '25 14:07 vjik