bmewburn / vscode-intelephense

PHP intellisense for Visual Studio Code
https://intelephense.com
Other
1.56k stars 92 forks source link

Potentially incomplete behavior inheriting generics #2896

Closed Xuerian closed 4 days ago

Xuerian commented 4 weeks ago

Describe the bug I am using query builders that I implement as iterable collections that I extend to provide specific functionality for matching entity subtypes.

In practice, this works fine, but I am having difficulty figuring out if I am attempting to annotate this correctly or if it isn't working.

To Reproduce

/**
 * @template T
 */
class A_Collection
{
    /**
     * @param class-string<T> $class
     * @return static<T>
     */
    public function __construct(
        public $class,
    ) {

    }
}

/**
 * @extends A_Collection<C>
 */
class C_Collection
extends A_Collection
{

}

class A
{
    static function collection()
    {
        return new A_Collection(static::class);
    }

    static function wrapCollection()
    {
        return static::collection();
    }
}

Class B
extends A
{

}

Class C
extends A
{
    static function collection()
    {
        return new C_Collection(static::class);
    }

    static function extendedWrapCollection()
    {
        return static::wrapCollection();
    }
}

# Expecting: A_Collection<A>
$A = A::collection();
# A_Collection<A>
$Aw = A::wrapCollection();
# A_Collection<A>

# Expecting: A_Collection<B>
$B = B::collection();
# A_Collection<A>
$Bw = B::wrapCollection();
# A_Collection<A>

# Expecting: C_Collection<C>
$C = C::collection();
# C_Collection
$Cw = C::wrapCollection();
# A_Collection<A>
$Cew = C::extendedWrapCollection();
# A_Collection<A>

Expected behavior This seems like enough type information that the calls should be inferred successfully, and during execution the expected results pan out.

When adding a signature to A::collection(), B::collection() now gives the expected result, but B::wrapCollection() does not, and C::collection becomes incorrect in a different way:

/**
 * @template T
 */
class A_Collection
{
    /**
     * @param class-string<T> $class
     * @return static<T>
     */
    public function __construct(
        public $class,
    ) {

    }
}

/**
 * @extends A_Collection
 */
class C_Collection
extends A_Collection
{

}

class A
{
    /**
     * @return A_Collection<static>
     */
    static function collection()
    {
        return new A_Collection(static::class);
    }

    static function wrapCollection()
    {
        return static::collection();
    }
}

Class B
extends A
{

}

Class C
extends A
{
    static function collection()
    {
        return new C_Collection(static::class);
    }

    static function extendedWrapCollection()
    {
        return static::wrapCollection();
    }
}

# Expecting: A_Collection<A>
$A = A::collection();
# A_Collection<A>
$Aw = A::wrapCollection();
# A_Collection<A>

# Expecting: A_Collection<B>
$B = B::collection();
# A_Collection<B>
$Bw = B::wrapCollection();
# A_Collection<A>

# Expecting: C_Collection<C>
$C = C::collection();
# A_Collection<C>
$Cw = C::wrapCollection();
# A_Collection<A>
$Cew = C::extendedWrapCollection();
# A_Collection<A>

Adding a similar annotation to C::collection() yields the expected result for that call, but similarly does not impact the wrapped calls.

Platform and version Bazzite/Fedora 39, Flatpak Intelephense v1.10.4