diff --git a/src/Psalm/Checker/FunctionLike/ReturnTypeCollector.php b/src/Psalm/Checker/FunctionLike/ReturnTypeCollector.php index ae6f0847f..c33031f95 100644 --- a/src/Psalm/Checker/FunctionLike/ReturnTypeCollector.php +++ b/src/Psalm/Checker/FunctionLike/ReturnTypeCollector.php @@ -35,22 +35,22 @@ class ReturnTypeCollector if ($stmt->expr instanceof PhpParser\Node\Expr\Yield_ || $stmt->expr instanceof PhpParser\Node\Expr\YieldFrom) { $yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($stmt->expr)); - } else { - if (!$stmt->expr) { - $return_types[] = new Atomic\TVoid(); - } elseif (isset($stmt->inferredType)) { - $return_types = array_merge(array_values($stmt->inferredType->getTypes()), $return_types); + } - if ($stmt->inferredType->ignore_nullable_issues) { - $ignore_nullable_issues = true; - } + if (!$stmt->expr) { + $return_types[] = new Atomic\TVoid(); + } elseif (isset($stmt->inferredType)) { + $return_types = array_merge(array_values($stmt->inferredType->getTypes()), $return_types); - if ($stmt->inferredType->ignore_falsable_issues) { - $ignore_falsable_issues = true; - } - } else { - $return_types[] = new Atomic\TMixed(); + if ($stmt->inferredType->ignore_nullable_issues) { + $ignore_nullable_issues = true; } + + if ($stmt->inferredType->ignore_falsable_issues) { + $ignore_falsable_issues = true; + } + } else { + $return_types[] = new Atomic\TMixed(); } break; @@ -80,6 +80,15 @@ class ReturnTypeCollector $ignore_falsable_issues ) ); + } elseif ($stmt instanceof PhpParser\Node\Stmt\Expression + && ($stmt->expr instanceof PhpParser\Node\Expr\MethodCall + || $stmt->expr instanceof PhpParser\Node\Expr\FuncCall + || $stmt->expr instanceof PhpParser\Node\Expr\StaticCall + ) + ) { + foreach ($stmt->expr->args as $arg) { + $yield_types = array_merge($yield_types, self::getYieldTypeFromExpression($arg->value)); + } } elseif ($stmt instanceof PhpParser\Node\Stmt\If_) { $return_types = array_merge( $return_types, @@ -212,19 +221,27 @@ class ReturnTypeCollector foreach ($yield_types as $type) { if ($type instanceof Type\Atomic\TArray || $type instanceof Type\Atomic\TGenericObject) { - $first_type_param = count($type->type_params) ? $type->type_params[0] : null; - $last_type_param = $type->type_params[count($type->type_params) - 1]; + switch (count($type->type_params)) { + case 1: + $key_type_param = Type::getMixed(); + $value_type_param = $type->type_params[1]; + break; - if ($value_type === null) { - $value_type = clone $last_type_param; - } else { - $value_type = Type::combineUnionTypes($value_type, $last_type_param); + default: + $key_type_param = $type->type_params[0]; + $value_type_param = $type->type_params[1]; } - if (!$key_type || !$first_type_param) { - $key_type = $first_type_param ? clone $first_type_param : Type::getMixed(); + if (!$key_type) { + $key_type = clone $key_type_param; } else { - $key_type = Type::combineUnionTypes($key_type, $first_type_param); + $key_type = Type::combineUnionTypes($key_type_param, $key_type); + } + + if (!$value_type) { + $value_type = clone $value_type_param; + } else { + $value_type = Type::combineUnionTypes($value_type_param, $value_type); } } } @@ -235,6 +252,8 @@ class ReturnTypeCollector [ $key_type ?: Type::getMixed(), $value_type ?: Type::getMixed(), + Type::getMixed(), + $return_types ? new Type\Union($return_types) : Type::getVoid() ] ), ]; @@ -272,8 +291,8 @@ class ReturnTypeCollector return [new Atomic\TMixed()]; } elseif ($stmt instanceof PhpParser\Node\Expr\YieldFrom) { - if (isset($stmt->inferredType)) { - return array_values($stmt->inferredType->getTypes()); + if (isset($stmt->expr->inferredType)) { + return array_values($stmt->expr->inferredType->getTypes()); } return [new Atomic\TMixed()]; diff --git a/src/Psalm/Checker/Statements/ExpressionChecker.php b/src/Psalm/Checker/Statements/ExpressionChecker.php index 92c094010..ee16e585d 100644 --- a/src/Psalm/Checker/Statements/ExpressionChecker.php +++ b/src/Psalm/Checker/Statements/ExpressionChecker.php @@ -1132,8 +1132,22 @@ class ExpressionChecker } if (isset($stmt->expr->inferredType)) { + $yield_from_type = null; + + foreach ($stmt->expr->inferredType->getTypes() as $atomic_type) { + if ($yield_from_type === null + && $atomic_type instanceof Type\Atomic\TGenericObject + && strtolower($atomic_type->value) === 'generator' + && isset($atomic_type->type_params[3]) + ) { + $yield_from_type = clone $atomic_type->type_params[3]; + } else { + $yield_from_type = Type::getMixed(); + } + } + // this should be whatever the generator above returns, but *not* the return type - $stmt->inferredType = Type::getMixed(); + $stmt->inferredType = $yield_from_type ?: Type::getMixed(); } return null; diff --git a/src/Psalm/Checker/TypeChecker.php b/src/Psalm/Checker/TypeChecker.php index b931abee2..33b20f480 100644 --- a/src/Psalm/Checker/TypeChecker.php +++ b/src/Psalm/Checker/TypeChecker.php @@ -797,10 +797,6 @@ class TypeChecker foreach ($input_type_part->type_params as $i => $input_param) { if (!isset($container_type_part->type_params[$i])) { - $type_coerced = true; - $type_coerced_from_mixed = true; - - $all_types_contain = false; break; } diff --git a/src/Psalm/Stubs/CoreGenericClasses.php b/src/Psalm/Stubs/CoreGenericClasses.php index 2961e10ae..706a59262 100644 --- a/src/Psalm/Stubs/CoreGenericClasses.php +++ b/src/Psalm/Stubs/CoreGenericClasses.php @@ -88,6 +88,8 @@ interface Iterator extends Traversable { /** * @template TKey * @template TValue + * @template TSend + * @template TReturn * * @template-extends Traversable */ @@ -117,8 +119,15 @@ class Generator implements Traversable { */ public function rewind() {} + /** + * @return TReturn Can return any type. + */ public function getReturn() {} + /** + * @param TSend $value + * @return TValue Can return any type. + */ public function send($value) {} public function throw(Exception $exception) {} diff --git a/tests/Php70Test.php b/tests/Php70Test.php index d1edbffac..0dbf26e3f 100644 --- a/tests/Php70Test.php +++ b/tests/Php70Test.php @@ -202,8 +202,7 @@ class Php70Test extends TestCase 'generatorDelegation' => [ ' - * @psalm-generator-return int + * @return Generator */ function count_to_ten(): Generator { yield 1; @@ -215,7 +214,7 @@ class Php70Test extends TestCase } /** - * @return Generator + * @return Generator */ function seven_eight(): Generator { yield 7; @@ -230,8 +229,7 @@ class Php70Test extends TestCase } /** - * @return Generator - * @psalm-generator-return int + * @return Generator */ function nine_ten(): Generator { yield 9; @@ -244,8 +242,8 @@ class Php70Test extends TestCase } $gen2 = $gen->getReturn();', 'assertions' => [ - '$gen' => 'Generator', - '$gen2' => 'mixed', + '$gen' => 'Generator', + '$gen2' => 'int', ], 'error_levels' => ['MixedAssignment'], ], @@ -295,6 +293,36 @@ class Php70Test extends TestCase yield 2; }', ], + 'returnType' => [ + ' + */ + function other_generator() : Generator { + yield "traffic"; + return 1; + } + + /** + * @return Generator + */ + function foo() : Generator { + $a = yield from other_generator(); + takesInt($a); + } + + foreach (foo() as $s) { + takesString($s); + }', + ], ]; }