phpstan / phpstan

PHP Static Analysis Tool - discover bugs in your code without running it!
https://phpstan.org/
MIT License
12.85k stars 870 forks source link

Type with nested generic parameter is not possible to resolve #4642

Closed hrach closed 3 years ago

hrach commented 3 years ago

Bug report

When there is a nested generic parameter, resolution from subtype string-class is not done.

interface IEntity {}
/** @template E of IEntity */
interface IRepository {}

interface I {
    /**
    * Returns repository by repository class.
    * @template E of IEntity
    * @template T of IRepository<E>
    * @phpstan-param class-string<T> $className
    * @phpstan-return T
    */
    function getRepository(string $className): IRepository;
}

class User implements IEntity {}
/** @implements IRepository<User> */
class UsersRepository implements IRepository {}

function test(I $model): void {
    $model->getRepository(UsersRepository::class);
}

Results to Unable to resolve the template type T in call to method Nextras\Orm\Model\IModel::getRepository(). Changing the getRepository signature to @template T of IRepository (removed <E>) helps but its not correct and produce error about missing the IRepository generic parameter.

Code snippet that reproduces the problem

https://phpstan.org/r/4b0bfe9f-4e01-4067-b4fb-e65c11e9da57

Expected output

Ability to resolve return type to AuthorsRepository instance.

ondrejmirtes commented 3 years ago

Right now you need @template-covariant for this to work: https://phpstan.org/r/e052aaeb-56bf-4492-ab0b-592706e0346b

I'm still not sure if i want to have it working with invariant...

hrach commented 3 years ago
ondrejmirtes commented 3 years ago

Never mind, I figured out the problem in my sleep 😂 https://github.com/phpstan/phpstan-src/commit/48aea56c5ed8fda80caf3d1dbaa8b9dd2c8fd301

hrach commented 3 years ago

Wow! Thanks. Seeing the change amazes me. one thing is to understand the concept but fixing this... 🤯

ondrejmirtes commented 3 years ago

The problem was that IRepository<IEntity> isn't a supertype of UsersRepository when E isn't covariant. But last night I realized that I should first resolve E, replace it in the bound and only after that ask whether it's a supertype. That way it works even when E is invariant.

hrach commented 3 years ago

first resolve E, replace it in the bound and only after that ask whether it's a supertype. That way it works even when E is invariant.

Something like this could be a comment in the source code :) Anyway, thank you :)

github-actions[bot] commented 3 years ago

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.