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 inference of template definition using nested templates #8112

Closed
emmanuelGuiton opened this issue Jun 14, 2022 · 8 comments
Closed

Comments

@emmanuelGuiton
Copy link
Contributor

Hi,

I would expected the 2 following snippets to work :
https://psalm.dev/r/f444f78efa
https://psalm.dev/r/f8c537da60

It seems Psalm does not get that it has the same template definition in Utils::aFunction() and in the Repository interface.
Am I missing anything ?

Regards,

  • Emmanuel
@psalm-github-bot
Copy link

I found these snippets:

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

abstract class Id {}

/**
 * @template I of Id
 */
interface ObjectSpec {}

/**
 * @template I of Id
 * @template O of ObjectSpec<I>
 */
interface Repository{}

class Utils
{
	/**
	 * @template I of Id
	 * @template O of ObjectSpec<I>
	 * @psalm-param Repository<I, O> $repo
	 * @return void
	 */
	public static function aFunction(Repository $repo): void {}
}
Psalm output (using commit 10ea05a):

ERROR: InvalidTemplateParam - 21:18 - Extended template param O of Repository<I:fn-utils::afunction as Id, O:fn-utils::afunction as ObjectSpec<I:fn-utils::afunction as Id>> expects type ObjectSpec<I:Repository as Id>, type O:fn-utils::afunction as ObjectSpec<I:fn-utils::afunction as Id> given
https://psalm.dev/r/f8c537da60
<?php

abstract class Id {}

/**
 * @template I of Id
 */
interface ObjectSpec {}

/**
 * @template I of Id
 * @template O of ObjectSpec<I>
 */
interface Repository{}

/**
 * @template I of Id
 * @template O of ObjectSpec<I>
 */
class Utils
{
	/**
	 * @psalm-param Repository<I, O> $repo
	 * @return void
	 */
	public function aFunction(Repository $repo): void {}
}
Psalm output (using commit 10ea05a):

ERROR: InvalidTemplateParam - 23:18 - Extended template param O of Repository<I:Utils as Id, O:Utils as ObjectSpec<I:Utils as Id>> expects type ObjectSpec<I:Repository as Id>, type O:Utils as ObjectSpec<I:Utils as Id> given

@AndrolGenhald
Copy link
Collaborator

I think this is another case where Psalm's support for using nested templates is buggy 🙁

@emmanuelGuiton
Copy link
Contributor Author

emmanuelGuiton commented Nov 24, 2022

It seems I have yet another case : https://psalm.dev/r/dd644437de
Is there any work around ?

Note that though the Psalm issue points to the parameter O of AnEntity<I, O>, it disappears if extends DbEntityRepository is removed : https://psalm.dev/r/ad8477b7ea
It seems the Psalm is loosing control because of the parent class.

@psalm-github-bot
Copy link

psalm-github-bot bot commented Nov 24, 2022

I found these snippets:

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

// DATA MODEL
/** @template T */
interface AnId
{
	/**
	 * Getter for the internal value.
	 *
	 * @return T Internal value.
	 */
	public function getValue();
}

/** @template I of AnId */
abstract class AnObject
{
   	/**
	 * Getter for the identifier.
	 *
	 * @psalm-return I The identifier
	 */
	abstract public function getId();
}

// PERSISTENCE

/** @template T of object */
interface DbEntity
{
	/**
	 * Maps this entity to a data-model object
	 *
	 * @return object Data-model object to which this entity maps.
	 * @psalm-return T
	 */
	public function toModel(): object;
}

/**
 * Interface for all entities that map to data model object with an identifier.
 *
 * @template I of AnId
 * @template O of AnObject<I>
 * @extends DbEntity<O>
 */
interface AnEntity
extends DbEntity {}

/**
 * Repository of database entities.
 * @template T of object
 * @template E of DbEntity<T>
 */
abstract class DbEntityRepository {}

/**
 * Repository of database entities that maps to data model objects with identifiers.
 * @template I of AnId
 * @template O of AnObject<I>
 * @template E of AnEntity<I, O>
 * @extends DbEntityRepository<O, E>
 */
abstract class AnEntityRepository
extends DbEntityRepository
{}
Psalm output (using commit 8f39de9):

ERROR: InvalidTemplateParam - 64:16 - Extended template param O of AnEntity<I:AnEntityRepository as AnId, O:AnEntityRepository as AnObject<I:AnEntityRepository as AnId>> expects type AnObject<I:AnEntity as AnId>, type O:AnEntityRepository as AnObject<I:AnEntityRepository as AnId> given
https://psalm.dev/r/ad8477b7ea
<?php

// DATA MODEL
/** @template T */
interface AnId
{
	/**
	 * Getter for the internal value.
	 *
	 * @return T Internal value.
	 */
	public function getValue();
}

/** @template I of AnId */
abstract class AnObject
{
   	/**
	 * Getter for the identifier.
	 *
	 * @psalm-return I The identifier
	 */
	abstract public function getId();
}

// PERSISTENCE

/** @template T of object */
interface DbEntity
{
	/**
	 * Maps this entity to a data-model object
	 *
	 * @return object Data-model object to which this entity maps.
	 * @psalm-return T
	 */
	public function toModel(): object;
}

/**
 * Interface for all entities that map to data model object with an identifier.
 *
 * @template I of AnId
 * @template O of AnObject<I>
 * @extends DbEntity<O>
 */
interface AnEntity
extends DbEntity {}

/**
 * Repository of database entities.
 * @template T of object
 * @template E of DbEntity<T>
 */
abstract class DbEntityRepository {}

/**
 * Repository of database entities that maps to data model objects with identifiers.
 * @template I of AnId
 * @template O of AnObject<I>
 * @template E of AnEntity<I, O>
 */
abstract class AnEntityRepository
{}
Psalm output (using commit 8f39de9):

No issues!

@emmanuelGuiton
Copy link
Contributor Author

emmanuelGuiton commented Nov 24, 2022

I think the simplest case to reproduce the issue is that one : https://psalm.dev/r/6531496dba
Psalm behaves correctly when there are 2 nested templates, it fails with 3 nested templates.

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/73c1e5aa67
<?php

/** @template T */
interface A {}

/**
 * @template T
 * @template U of A<T>
 */
interface B {}

/**
 * @template T
 * @template U of A<T>
 * @template V of B<T, U>
 */
interface C {}
     
/** @template T */
interface J {}
     
/**
 * @template T
 * @template U of A<T>
 * @template V of B<T, U>
 * @implements J<V>
 */
class K1 implements J {} // class with 3 nested template arguments : KO
     
/**
 * @template T
 * @template U of A<T>
 * @implements J<U>
 */
class K2 implements J {} // class with 2 nested template arguments : OK

/**
 * @template T
 * @template U of A<T>
 * @template V of B<T, U>
 * @extends J<V>
 */
interface K3 extends J {} // interface with 3 nested template arguments : OK
Psalm output (using commit 8f39de9):

ERROR: InvalidTemplateParam - 28:7 - Extended template param U of B<T:K1 as mixed, U:K1 as A<T:K1 as mixed>> expects type A<T:B as mixed>, type U:K1 as A<T:K1 as mixed> given

@emmanuelGuiton emmanuelGuiton changed the title Incorrect inference of template definition ? Incorrect inference of template definition using nested templates Nov 24, 2022
@emmanuelGuiton
Copy link
Contributor Author

I was wrong, here is a definitely simpler example : https://psalm.dev/r/a89a8f3138

@psalm-github-bot
Copy link

I found these snippets:

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

/** @template T */
interface A {}

/**
 * @template T
 * @template U of A<T>
 */
interface B {}
     
/** @template T */
interface J {}
     
/**
 * @implements J<B<object, A<object>>>
 */
class K1 implements J {} // class with 3 nested template arguments : KO
Psalm output (using commit 8f39de9):

ERROR: InvalidTemplateParam - 18:7 - Extended template param U of B<object, A<object>> expects type A<T:B as mixed>, type A<object> given

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants