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

Psalm unable to infer generic type when templated class implements a templated interface #2928

Closed
ocrampete16 opened this issue Mar 6, 2020 · 4 comments
Labels

Comments

@ocrampete16
Copy link

I've created a snippet to illustrate the issue: https://psalm.dev/r/557d8b895c

The first foreach block:

/** @var Collection<int, string> $foo */
$foo = new FooCollection(['foo']);
foreach ($foo as $bar) {
  $bar + 1;
}

results in Cannot perform a numeric operation with a non-numeric type string. So Psalm knows that $bar is a string. So far so good.

When I use the concrete class in the type hint (or leave the docblock out completely), Psalm is unable to infer that $bar is a string instead of mixed, even though it knows that it is of type FooCollection<int, string>:

/** @var FooCollection<int, string> $foo */
$foo = new FooCollection(['foo']);
foreach ($foo as $bar) {
  $bar + 1;
}

Running Psalm with the second foreach block gives me:

INFO: MixedAssignment - 45:18 - Cannot assign $bar to a mixed type

INFO: MixedOperand - 46:3 - Left operand cannot be mixed
@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/557d8b895c
<?php

/**
 * @psalm-template TKey of array-key
 * @psalm-template T
 * @template-extends IteratorAggregate<TKey, T>
 */
interface Collection extends IteratorAggregate {}

/**
 * @psalm-template TKey of array-key
 * @psalm-template T
 * @template-implements Collection<TKey,T>
 */
class FooCollection implements Collection
{
  private $elements;
  
  /**
   * @psalm-param array<TKey,T> $elements
   */
  public function __construct(array $elements)
  {
    $this->elements = $elements;
  }
  
  public function getIterator()
  {
    return new ArrayIterator($this->elements);
  }
}

// Psalm knows that $bar is a string.
/** @var Collection<int, string> $foo */
$foo = new FooCollection(['foo']);
foreach ($foo as $bar) {
  $bar + 1;
}

// But when using the implementation in the type hint, $bar's type can't be inferred.
// Even though Psalm knows that $foo is of a "non-numeric type FooCollection<int, string>"
/** @var FooCollection<int, string> $foo */
$foo = new FooCollection(['foo']);
foreach ($foo as $bar) {
  $bar + 1;
}
Psalm output (using commit 352bd3f):

ERROR: InvalidOperand - 37:3 - Cannot perform a numeric operation with a non-numeric type string

INFO: MixedAssignment - 44:18 - Cannot assign $bar to a mixed type

INFO: MixedOperand - 45:3 - Left operand cannot be mixed

@muglug
Copy link
Collaborator

muglug commented Mar 6, 2020

You can fix this by adding a return type to getIterator: https://psalm.dev/r/3f8b934c61

Still not sure if this is a bug, though

@psalm-github-bot
Copy link

I found these snippets:

https://psalm.dev/r/3f8b934c61
<?php

/**
 * @psalm-template TKey of array-key
 * @psalm-template T
 * @template-extends IteratorAggregate<TKey, T>
 */
interface Collection extends IteratorAggregate {}

/**
 * @psalm-template TKey of array-key
 * @psalm-template T
 * @template-implements Collection<TKey,T>
 */
class FooCollection implements Collection
{
  private $elements;
  
  /**
   * @psalm-param array<TKey,T> $elements
   */
  public function __construct(array $elements) {
    $this->elements = $elements;
  }
  
  /** @return ArrayIterator<TKey, T> */
  public function getIterator()
  {
    return new ArrayIterator($this->elements);
  }
}

/** @var Collection<int, string> $foo */
$foo = new FooCollection(['foo']);
foreach ($foo as $bar) {
  $bar + 1;
}

/** @var FooCollection<int, string> $foo */
$foo = new FooCollection(['foo']);
foreach ($foo as $bar) {
  $bar + 1;
}
Psalm output (using commit 352bd3f):

ERROR: InvalidOperand - 36:3 - Cannot perform a numeric operation with a non-numeric type string

ERROR: InvalidOperand - 42:3 - Cannot perform a numeric operation with a non-numeric type string

@muglug muglug added the bug label Mar 6, 2020
@muglug muglug closed this as completed in b999037 Mar 7, 2020
@ocrampete16
Copy link
Author

Awesome, thanks for the swift fix!

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

No branches or pull requests

2 participants