mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Fix #306 - add support for typing generators
This commit is contained in:
parent
a8a504c3b9
commit
4172952e8b
@ -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()];
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {}
|
||||
|
@ -202,8 +202,7 @@ class Php70Test extends TestCase
|
||||
'generatorDelegation' => [
|
||||
'<?php
|
||||
/**
|
||||
* @return Generator<int,int>
|
||||
* @psalm-generator-return int
|
||||
* @return Generator<int, int, mixed, int>
|
||||
*/
|
||||
function count_to_ten(): Generator {
|
||||
yield 1;
|
||||
@ -215,7 +214,7 @@ class Php70Test extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator<int,int>
|
||||
* @return Generator<int, int>
|
||||
*/
|
||||
function seven_eight(): Generator {
|
||||
yield 7;
|
||||
@ -230,8 +229,7 @@ class Php70Test extends TestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator<int,int>
|
||||
* @psalm-generator-return int
|
||||
* @return Generator<int,int, mixed, int>
|
||||
*/
|
||||
function nine_ten(): Generator {
|
||||
yield 9;
|
||||
@ -244,8 +242,8 @@ class Php70Test extends TestCase
|
||||
}
|
||||
$gen2 = $gen->getReturn();',
|
||||
'assertions' => [
|
||||
'$gen' => 'Generator<int, int>',
|
||||
'$gen2' => 'mixed',
|
||||
'$gen' => 'Generator<int, int, mixed, int>',
|
||||
'$gen2' => 'int',
|
||||
],
|
||||
'error_levels' => ['MixedAssignment'],
|
||||
],
|
||||
@ -295,6 +293,36 @@ class Php70Test extends TestCase
|
||||
yield 2;
|
||||
}',
|
||||
],
|
||||
'returnType' => [
|
||||
'<?php
|
||||
function takesInt(int $i) : void {
|
||||
echo $i;
|
||||
}
|
||||
|
||||
function takesString(string $s) : void {
|
||||
echo $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator<int, string, mixed, int>
|
||||
*/
|
||||
function other_generator() : Generator {
|
||||
yield "traffic";
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Generator<int, string>
|
||||
*/
|
||||
function foo() : Generator {
|
||||
$a = yield from other_generator();
|
||||
takesInt($a);
|
||||
}
|
||||
|
||||
foreach (foo() as $s) {
|
||||
takesString($s);
|
||||
}',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user