Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect type expected when using static with generics #9407

Closed
lkrms opened this issue Jun 7, 2023 · 4 comments
Closed

Incorrect type expected when using static with generics #9407

lkrms opened this issue Jun 7, 2023 · 4 comments

Comments

@lkrms
Copy link

lkrms commented Jun 7, 2023

Bug report

With a generic like:

/**
 * @template T
 */
class Rules {}

Using types like:

/**
 * @return Rules<static>
 */

Leads to unresolvable errors like:

Parameter #1 $rules of method Entity::_toArray() expects Rules<Entity>, Rules<static(Entity)> given.

There's no way to annotate arguments and return values to make PHPStan believe Rules<static(Entity)> is expected and/or to indicate Rules<Entity> is returned.

(The example makes it clearer.)

Code snippet that reproduces the problem

https://phpstan.org/r/4c2e877c-61a9-4fb0-9551-0684f402550f

Expected output

The example code shouldn't trigger any errors.

@ondrejmirtes
Copy link
Member

The playground is telling you exactly what's wrong and links this article: https://phpstan.org/blog/whats-up-with-template-covariant

@lkrms
Copy link
Author

lkrms commented Jun 7, 2023

Thanks for responding so quickly, @ondrejmirtes :)

I did read the linked article, but (still) don't understand how/why this code needs a covariant template when the types it appears in are explicitly identical. Are you able to clarify what the trigger for this scenario is, please? I wasn't able to find documentation about PHPStan's evaluation of static in this context or how it establishes the need for covariance. Apologies if I've missed it 😬

@lkrms
Copy link
Author

lkrms commented Jun 8, 2023

Actually, I think understand:

<?php

/**
 * @template T
 */
class R
{
    public string $Class;

    /**
     * @var array<string,mixed>
     */
    public array $Data = [];

    /**
     * @param class-string<T> $class
     */
    public function __construct(string $class)
    {
        $this->Class = $class;
    }

    /**
     * @param R<T> $r
     * @return $this
     */
    public function merge(R $r)
    {
        $this->Data += $r->Data;
        return $this;
    }
}

class A
{
    /**
     * @var R<static>
     */
    public R $Value;

    /**
     * @return R<static>
     */
    public static function getValue(): R
    {
        return new R(static::class);
    }

    /**
     * @param R<static> $r
     * @return R<static>
     */
    public function changeValue(R $r): R
    {
        return $r;
    }

    /**
     * @param R<static> $r
     * @return $this
     */
    public function withValue(R $r)
    {
        $this->Value = $r;
        return $this;
    }

    /**
     * @return $this
     */
    public function doSomething()
    {
        return $this->withValue(static::getValue());
    }
}

class B extends A {}

class C extends B {}

(Playground here.)

If I understand the semantics of PHPStan's static(Type) correctly, this fails because in the context of B, R<static> (expressed as R<static(B)>) could mean R<B> or R<C>, which is narrower than the same type in the context of A, i.e. R<static(A)>, which could mean R<A>, R<B> or R<C>.

Am I correct so far?

If so, this implicit type narrowing is what was missing from my understanding, and explains why there are issues when values of type static are chained together (although I'm still not sure why I'm getting Parameter #1 $rules of method XXX expects Rules<Entity>, Rules<static(Entity)> given, because I would have thought the narrower value would be accepted by a parameter with a wider type; interestingly, the same errors are reported when I use $this instead of static in return and parameter types).

Anyway, assuming that's all working as intended, after adopting @template-covariant in the example above, I immediately hit an issue with the template appearing in a contravariant position, so if using covariant templates is the solution, I'm interested in what a best-practice approach to resolving contravariant positions might be.

(Playground here.)

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 10, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants