-
Notifications
You must be signed in to change notification settings - Fork 664
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
UndefinedDocblockClass when using template in static method #2571
Comments
I found these snippets: https://psalm.dev/r/6d29fb978b<?php
/**
* @template T
*/
abstract class Foo {
/**
* @psalm-param T $data
*/
public static function create($data) {
}
}
|
This is intentional: 797a059 Can you explain what you're trying to achieve? Most likely you want |
I found these snippets: https://psalm.dev/r/340c710984<?php
/** @template TT */
class Foo {
/**
* @template T
* @psalm-param T $data
* @return self<T>
*/
public static function create($data) {
return new self;
}
}
atan(Foo::create(22));
|
This was the minimum example which replicated the issue itself. But I was dealing with an abstract class which had a final factory method. The inheriting classes defined the type. But yeah seems that I can solve it with your proposal. 👍 |
We experienced a similar problem in redaxo/redaxo#4359 in which we wanted to template a trait with a single type across all its methods |
Accoring to vimeo/psalm#2571, psalm doesn't support it
I wanted to use a template on a param of a static method in Enum class from myclabs/php-enum. Template there defines a value type in a "enum"; template in expanded in child class; parent's Here is a minimal example if a file there is too big to read: https://psalm.dev/r/402a8f5b50 |
I found these snippets: https://psalm.dev/r/402a8f5b50<?php
/**
* @template T
*/
class Enum {
/**
* @param mixed $bar
* @psalm-param T $value
* @return static
*/
public static function from($value): self
{
// Actually it looks up an enum entry by value but this is just a stub
return new static();
}
// ...
// Here go more magic methods that make methods from annotation below work
}
/**
* @method static self RED()
* @method static self GREEN()
* @extends Enum<string>
*/
class TrafficLightSignals extends Enum {
private const RED = 'red';
private const GREEN = 'green';
}
TrafficLightSignals::from('red');
|
You can't mix class templates and static templates by design. Would you mind providing a more detailed reproducer (where usage of $value is visible) on a new Issue if you have questions? |
@weirdan @orklah Here is an example in my project where I need the behaviour you mentioned as being handled that way by design: /** @psalm-immutable */
abstract class Id implements \Stringable
{
final public function __construct(
protected readonly string $value,
) {
if (!uuid_is_valid($value)) {
throw new InvalidId($value);
}
}
}
/** @psalm-immutable */
class UserId extends Id
{
}
/**
* @template T extends Id
*
* @template-implements \IteratorAggregate<int, T>
*/
abstract class IdList implements \IteratorAggregate, \Countable
{
/** @var array<int, T> */
public readonly array $ids;
/** @param array<int, T> $ids */
final public function __construct(
array $ids,
) {
$this->ids = array_values($ids);
}
/** @return class-string<T> */
abstract public static function handlesIdClass(): string;
}
/** @extends IdList<UserId> */
final class UserIdList extends IdList
{
public static function handlesIdClass(): string
{
return UserId::class;
}
} The only workaround I found for it is adding the following: /**
* @template TT of T
*
* @return class-string<TT>
*/
abstract public static function handlesIdClass(): string; But this then breaks PHPStan, because it interpretes the What was the reason for disabling the template syntax for static methods? I only see the commit, but don't know why this was done. Would this be a valid example to reconsider the previous decision? 🙂 |
Based on your example, let's say I create a I then make sure to make an array of I have a What you did is not completely safe from a type POV. You lied on the template you're returning because the information you have on a static method is not enough to fully describe the template of what's inside your List. To be able to describe it accurately, you'd need to have access to the list itself, so you can only do that from an instance, not from a static method. This is this kind of confusion that lead Psalm to stop allowing class level template on static methods. I think this could be solved in two ways for your specific example:
However, I don't have enough hindsight to know if it would be enough to solve the confusion for all situations here |
Thanks for your extensive reply. I'm not sure whether I can follow your logic 100%. I like the example of yours but I think there are two relevant notes:
You are correct, that using /** @var int $test */
$test = 'bla'; and Psalm won't complain there. At the moment I can't use Psalm to configure a valid behaviour. Aren't there more cases where it is useful than harmful? |
While trying to make a working example, I realized two mistakes I made:
|
I found these snippets: https://psalm.dev/r/dc8b18bcbd<?php
/** @psalm-immutable */
abstract class Id
{
final public function __construct(
protected readonly string $value,
) {
}
}
/** @psalm-immutable */
class UserId extends Id
{
}
/**
* @template T extends Id
*
* @template-implements \IteratorAggregate<int, T>
*/
abstract class IdList implements \IteratorAggregate, \Countable
{
/** @var array<int, T> */
public readonly array $ids;
/** @param array<int, T> $ids */
final public function __construct(
array $ids,
) {
$this->ids = array_values($ids);
}
/** @return class-string<T> */
abstract public static function handlesIdClass(): string;
}
/** @extends IdList<UserId> */
final class UserIdList extends IdList
{
public static function handlesIdClass(): string
{
return UserId::class;
}
}
$b = UserIdList::handlesIdClass();
/** @psalm-trace $b */;
|
Wow that is strange, it actually seems to work on Would you see the same kind of issues? Shouldn't this be a valid case even when using |
I found these snippets: https://psalm.dev/r/56b205a8cd<?php
/** @psalm-immutable */
abstract class Id
{
final public function __construct(
protected readonly string $value,
) {
}
}
/** @psalm-immutable */
class UserId extends Id
{
}
/**
* @template T extends Id
*
* @template-implements \IteratorAggregate<int, T>
*/
abstract class IdList implements \IteratorAggregate, \Countable
{
/** @var array<int, T> */
public readonly array $ids;
/** @param array<int, T> $ids */
final public function __construct(
array $ids,
) {
$this->ids = array_values($ids);
}
/** @return class-string<T> */
abstract public static function handlesIdClass(): string;
/** @param array<int, T> $ids */
final public static function fromIds(array $ids): static
{
return new static($ids);
}
/** @return \Iterator<int, T> */
public function getIterator(): \Iterator
{
return new \ArrayIterator($this->ids);
}
public function count(): int
{
return count($this->ids);
}
}
/** @extends IdList<UserId> */
final class UserIdList extends IdList
{
public static function handlesIdClass(): string
{
return UserId::class;
}
}
$b = UserIdList::handlesIdClass();
/** @psalm-trace $b */;
|
While trying to create factory methods I found out that templates are not correctly recognized in static methods.
See https://psalm.dev/r/6d29fb978b
The text was updated successfully, but these errors were encountered: