1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-22 13:51:54 +01:00
This commit is contained in:
Daniil Gentili 2022-10-13 13:42:35 +02:00
parent bb9aabe5b1
commit 8c9558c92b
6 changed files with 216 additions and 103 deletions

View File

@ -3,8 +3,10 @@
namespace Psalm\Internal\Analyzer\FunctionLike;
use PhpParser;
use PhpParser\NodeTraverser;
use Psalm\Codebase;
use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer;
use Psalm\Internal\PhpVisitor\YieldTypeCollector;
use Psalm\Internal\Provider\NodeDataProvider;
use Psalm\Type;
use Psalm\Type\Atomic\TArray;
@ -298,87 +300,19 @@ class ReturnTypeCollector
}
/**
* @return list<Union>
* @return list<Union>
*/
protected static function getYieldTypeFromExpression(
private static function getYieldTypeFromExpression(
PhpParser\Node\Expr $stmt,
NodeDataProvider $nodes
): array {
if ($stmt instanceof PhpParser\Node\Expr\Yield_) {
$key_type = null;
$collector = new YieldTypeCollector($nodes);
$traverser = new NodeTraverser();
$traverser->addVisitor(
$collector
);
$traverser->traverse([$stmt]);
if ($stmt->key && ($stmt_key_type = $nodes->getType($stmt->key))) {
$key_type = $stmt_key_type;
}
if ($stmt->value
&& $value_type = $nodes->getType($stmt->value)
) {
$generator_type = new TGenericObject(
'Generator',
[
$key_type ? clone $key_type : Type::getInt(),
clone $value_type,
Type::getMixed(),
Type::getMixed()
]
);
return [new Union([$generator_type])];
}
return [Type::getMixed()];
}
if ($stmt instanceof PhpParser\Node\Expr\YieldFrom) {
if ($stmt_expr_type = $nodes->getType($stmt->expr)) {
return [$stmt_expr_type];
}
return [Type::getMixed()];
}
if ($stmt instanceof PhpParser\Node\Expr\BinaryOp) {
return [
...self::getYieldTypeFromExpression($stmt->left, $nodes),
...self::getYieldTypeFromExpression($stmt->right, $nodes)
];
}
if ($stmt instanceof PhpParser\Node\Expr\Assign) {
return self::getYieldTypeFromExpression($stmt->expr, $nodes);
}
if ($stmt instanceof PhpParser\Node\Expr\MethodCall
|| $stmt instanceof PhpParser\Node\Expr\FuncCall
|| $stmt instanceof PhpParser\Node\Expr\StaticCall
|| $stmt instanceof PhpParser\Node\Expr\New_
) {
if ($stmt->isFirstClassCallable()) {
return [];
}
$yield_types = [];
foreach ($stmt->getArgs() as $arg) {
$yield_types = [...$yield_types, ...self::getYieldTypeFromExpression($arg->value, $nodes)];
}
return $yield_types;
}
if ($stmt instanceof PhpParser\Node\Expr\Array_) {
$yield_types = [];
foreach ($stmt->items as $item) {
if ($item instanceof PhpParser\Node\Expr\ArrayItem) {
$yield_types = [...$yield_types, ...self::getYieldTypeFromExpression($item->value, $nodes)];
}
}
return $yield_types;
}
return [];
return $collector->getYieldTypes();
}
}

View File

@ -9,6 +9,7 @@ use Psalm\Context;
use Psalm\Exception\DocblockParseException;
use Psalm\Internal\Analyzer\CommentAnalyzer;
use Psalm\Internal\Analyzer\FunctionLikeAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\AtomicPropertyFetchAnalyzer;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
@ -22,6 +23,8 @@ use Psalm\Type;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Atomic\TNamedObject;
use function array_values;
/**
* @internal
*/
@ -147,33 +150,69 @@ class YieldAnalyzer
$yield_type = null;
foreach ($expression_type->getAtomicTypes() as $expression_atomic_type) {
if ($expression_atomic_type instanceof TNamedObject) {
if (!$codebase->classlikes->classOrInterfaceExists($expression_atomic_type->value)) {
continue;
}
$classlike_storage = $codebase->classlike_storage_provider->get($expression_atomic_type->value);
if ($classlike_storage->yield) {
if ($expression_atomic_type instanceof TGenericObject) {
$yield_candidate_type = AtomicPropertyFetchAnalyzer::localizePropertyType(
$codebase,
clone $classlike_storage->yield,
$expression_atomic_type,
$classlike_storage,
$classlike_storage
);
$yield_type = Type::combineUnionTypes(
$yield_type,
$yield_candidate_type,
$codebase
);
} else {
$yield_type = Type::getMixed();
}
}
if (!$expression_atomic_type instanceof TNamedObject) {
continue;
}
if (!$codebase->classlikes->classOrInterfaceExists($expression_atomic_type->value)) {
continue;
}
$classlike_storage = $codebase->classlike_storage_provider->get($expression_atomic_type->value);
if (!$classlike_storage->yield) {
continue;
}
$declaring_classlike_storage = $classlike_storage->declaring_yield_fqcn
? $codebase->classlike_storage_provider->get($classlike_storage->declaring_yield_fqcn)
: $classlike_storage;
$yield_candidate_type = clone $classlike_storage->yield;
$yield_candidate_type = !$yield_candidate_type->isMixed()
? TypeExpander::expandUnion(
$codebase,
$yield_candidate_type,
$expression_atomic_type->value,
$expression_atomic_type->value,
null,
true,
false,
)
: $yield_candidate_type;
$class_template_params = ClassTemplateParamCollector::collect(
$codebase,
$declaring_classlike_storage,
$classlike_storage,
null,
new TNamedObject($expression_atomic_type->value),
true
);
if ($class_template_params) {
if (!$expression_atomic_type instanceof TGenericObject) {
$type_params = [];
foreach ($class_template_params as $type_map) {
$type_params[] = clone array_values($type_map)[0];
}
$expression_atomic_type = new TGenericObject($expression_atomic_type->value, $type_params);
}
$yield_candidate_type = AtomicPropertyFetchAnalyzer::localizePropertyType(
$codebase,
$yield_candidate_type,
$expression_atomic_type,
$classlike_storage,
$declaring_classlike_storage
);
}
$yield_type = Type::combineUnionTypes(
$yield_type,
$yield_candidate_type,
$codebase
);
}
if ($yield_type) {

View File

@ -618,6 +618,10 @@ class Populator
ClassLikeStorage $parent_storage,
bool $from_direct_parent
): void {
if ($parent_storage->yield && !$storage->yield) {
$storage->yield = $parent_storage->yield;
$storage->declaring_yield_fqcn ??= $parent_storage->name;
}
if ($parent_storage->template_types) {
$storage->template_extended_params[$parent_storage->name] = [];

View File

@ -0,0 +1,75 @@
<?php
namespace Psalm\Internal\PhpVisitor;
use PhpParser\Node;
use PhpParser\Node\Expr\YieldFrom;
use PhpParser\Node\Expr\Yield_;
use PhpParser\NodeVisitorAbstract;
use Psalm\Internal\Provider\NodeDataProvider;
use Psalm\Type;
use Psalm\Type\Atomic\TGenericObject;
use Psalm\Type\Union;
/**
* @internal
*/
class YieldTypeCollector extends NodeVisitorAbstract
{
/** @var list<Union> */
private array $yield_types = [];
private NodeDataProvider $nodes;
public function __construct(NodeDataProvider $nodes)
{
$this->nodes = $nodes;
}
public function enterNode(Node $stmt): ?Node
{
if ($stmt instanceof Yield_) {
$key_type = null;
if ($stmt->key && $stmt_key_type = $this->nodes->getType($stmt->key)) {
$key_type = $stmt_key_type;
}
if ($stmt->value
&& $value_type = $this->nodes->getType($stmt->value)
) {
$generator_type = new TGenericObject(
'Generator',
[
$key_type ? clone $key_type : Type::getInt(),
clone $value_type,
Type::getMixed(),
Type::getMixed()
]
);
$this->yield_types []= new Union([$generator_type]);
return null;
}
$this->yield_types []= Type::getMixed();
} elseif ($stmt instanceof YieldFrom) {
if ($stmt_expr_type = $this->nodes->getType($stmt->expr)) {
$this->yield_types []= $stmt_expr_type;
return null;
}
$this->yield_types []= Type::getMixed();
}
return null;
}
/**
* @return list<Union>
*/
public function getYieldTypes(): array
{
return $this->yield_types;
}
}

View File

@ -378,6 +378,9 @@ final class ClassLikeStorage implements HasAttributesInterface
*/
public $yield;
/** @var ?string */
public $declaring_yield_fqcn;
/**
* @var array<string, int>|null
*/

View File

@ -4044,6 +4044,64 @@ class ClassTemplateExtendsTest extends TestCase
return new Success("a");
}'
],
'yieldTemplatedComplex' => [
'code' => '<?php
/**
* @template T
* @psalm-yield T
*/
class a {
}
/**
* @template TT1
* @template TT2
* @extends a<TT2>
*/
class b extends a {}
/** @return Generator<int, b<"test1", "test2">, mixed, "test2"> */
function bb(): \Generator {
/** @var b<"test1", "test2"> */
$b = new b;
$result = yield $b;
return $result;
}'
],
'yieldTemplatedComplexResolved' => [
'code' => '<?php
/**
* @template T
* @psalm-yield T
*/
class a {
}
/**
* @extends a<"test">
*/
class b extends a {}
/** @return Generator<int, b, mixed, "test"> */
function bb(): \Generator {
$b = new b;
$result = yield $b;
return $result;
}'
],
'yieldTernary' => [
'code' => '<?php
/** @psalm-yield int */
class a {}
/**
* @return Generator<int, a, mixed, int>
*/
function a(): Generator {
return random_int(0, 1) ? 123 : yield new a;
}'
],
'multiLineTemplateExtends' => [
'code' => '<?php
interface IdInterface {}