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

Class template type on static method #5642

Open
olsavmic opened this issue Apr 19, 2021 · 1 comment
Open

Class template type on static method #5642

olsavmic opened this issue Apr 19, 2021 · 1 comment

Comments

@olsavmic
Copy link
Contributor

Hi,
We encountered a similar issue to #4258 and #2751.

I see that some work has been done to avoid this issue as in 50fc50f but there is still an issue with @param annotation.

Example of the issue (and a simplified use-case) is shown here: https://psalm.dev/r/cdf35ba896

The exact same code is working in PHPStan: https://phpstan.org/r/bcf918eb-626f-40cf-8b08-cbfa588f7b1f

Is there any reason why class level template parameters are forbidden in the @param annotation or is it a bug?

It's not possible to replace it with

  /**
   * @template TT
   * @param list<TT> $enums
   * @return static
   */
public static function ...

As that would loosen the type checking between the Enum and MultiEnum class.

I actually found a workaround using @template TT of T annotation instead on the class methods and I'm fine with such solution if it's the expected way of doing it but I'd at least add it to the docs.
(Workaround: https://psalm.dev/r/62775eb03c)

Thank you!

@psalm-github-bot
Copy link

I found these snippets:

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

class Enum {
    public function __construct(
        public int $val,
    ) {
    
    }
}

/** @template T of Enum */
interface MultiEnumWithPairInterface {
    
  /**
   * @param list<T> $enums
   * @return static
   */
  public static function createFromList(array $enums);
}

class MultiEnum {
    
	private int $value;
    
    /**
     * @param list<int> $values
     */
    public final function __construct(array $values) {
    	$this->value = array_reduce(
            $values,
            static fn(int $acc, int $val): int => $acc | $val, 
            0,
        );
    }
    
    /**
     * @return static
     */
    public static function createFromValue(int $val) {
        return new static([$val]);
    }
}


/** @template T */
trait MultiEnumWithPair {
    
  /**
   * @param list<T> $enums
   * @return static
   */
  public static function createFromList(array $enums) {
  		return static::createFromValue(1);
  }
  
  /**
   * @param int $data
   * @return static
   */
  public static abstract function createFromValue(int $val);
}

class EnumABC extends Enum {
}

class EnumDEF extends Enum {
}

/**
 * @template-implements MultiEnumWithPairInterface<EnumABC>
 */
class MultiEnumABC extends MultiEnum implements MultiEnumWithPairInterface {


    /**
     * @use MultiEnumWithPair<EnumABC>
     */
    use MultiEnumWithPair;
}

$a = MultiEnumABC::createFromList([new EnumDEF(1)]);
Psalm output (using commit 40b6860):

ERROR: InvalidArgument - 81:35 - Argument 1 of MultiEnumABC::createFromList expects list<T>, array{EnumDEF} provided

INFO: UnusedVariable - 81:1 - $a is never referenced or the value is not used

ERROR: UndefinedDocblockClass - 15:13 - Docblock-defined class or interface T does not exist

ERROR: UndefinedDocblockClass - 49:13 - Docblock-defined class or interface T does not exist

INFO: MixedInferredReturnType - 50:14 - Could not verify return type 'MultiEnumABC' for MultiEnumWithPair::createFromList
https://psalm.dev/r/62775eb03c
<?php

class Enum {
    public function __construct(
        public int $val,
    ) {
    
    }
}

/** @template T of Enum */
interface MultiEnumWithPairInterface {
    
  /**
   * @template TT of T
   * @param list<TT> $enums
   * @return static
   */
  public static function createFromList(array $enums);
}

class MultiEnum {
    
	private int $value;
    
    /**
     * @param list<int> $values
     */
    public final function __construct(array $values) {
    	$this->value = array_reduce(
            $values,
            static fn(int $acc, int $val): int => $acc | $val, 
            0,
        );
    }
    
    /**
     * @return static
     */
    public static function createFromValue(int $val) {
        return new static([$val]);
    }
}


/** @template T */
trait MultiEnumWithPair {
    
  /**
   * @template TT of T
   * @param list<TT> $enums
   * @return static
   */
  public static function createFromList(array $enums) {
  		return static::createFromValue(1);
  }
  
  /**
   * @param int $data
   * @return static
   */
  public static abstract function createFromValue(int $val);
}

class EnumABC extends Enum {
}

class EnumDEF extends Enum {
}

/**
 * @template-implements MultiEnumWithPairInterface<EnumABC>
 */
class MultiEnumABC extends MultiEnum implements MultiEnumWithPairInterface {


    /**
     * @use MultiEnumWithPair<EnumABC>
     */
    use MultiEnumWithPair;
}

$a = MultiEnumABC::createFromList([new EnumDEF(1)]);
Psalm output (using commit 40b6860):

ERROR: InvalidArgument - 83:35 - Argument 1 of MultiEnumABC::createFromList expects list<EnumABC>, array{EnumDEF} provided

INFO: UnusedVariable - 83:1 - $a is never referenced or the value is not used

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