Skip to content

incorrect InvalidReturnStatement when returning from an iterable generator. #11458

@azjezz

Description

@azjezz

Psalm incorrectly triggers an InvalidReturnStatement when a generator function, with a declared return type of iterable, uses a return statement with a value. It appears Psalm attempts to validate this return value against the iterable<K, V>, rather than treating it as the generator's R type.

If such a function has a declared return type like iterable<K, V> or iterable<V>, its actual signature is closer to Generator<K, V, S = mixed, R = mixed>.

The return $value; statement within a generator sets the R type parameter. This value is distinct from the iterable.

Currently, Psalm seems to incorrectly assume that $value in return $value; must conform to iterable<K, V>.

The following code demonstrates the issue:

<?php

/**
 * @param array<string, string> $array
 * @return iterable<string>
 */
function generator(array $array): iterable
{
    yield $array['key'] ?? 'default';

    return 1; // this is an error, because `1` does not match `iterable<string>`
}

/**
 * @param array<string, string> $array
 * @return iterable<string>
 */
function generator2(array $array): iterable
{
    yield $array['key'] ?? 'default';

    return []; // this is fine because `[]` matches `iterable<string>`
}

Suggested Resolution:

When analyzing a generator function with an iterable return type:

  • Recognize that return $value; defines the R type of the underlying Generator.
  • do not validate $value against the iterable<K, V>
  • If the R type needs to be statically checked, Psalm could perhaps assume R is mixed when only iterable is specified, or require the more explicit Generator type hint if the R type is important to the user.

The current behavior is misleading because the value from return in a generator is not meant to be an instance of the iterable type itself, but rather the result obtained from Generator::getReturn().

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions