1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-26 20:34:47 +01:00

Infer missing docblock-supplied types from constructor

Fixes #2071
This commit is contained in:
Brown 2019-08-27 10:37:39 -04:00
parent 3b865f6509
commit 1cb8c3f6c4
2 changed files with 93 additions and 57 deletions

View File

@ -1912,66 +1912,18 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$storage->returns_by_ref = true;
}
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod
&& $stmt->name->name === '__construct'
&& $stmt->stmts
&& $storage->params
&& $class_storage
&& $this->config->infer_property_types_from_constructor
) {
$assigned_properties = [];
foreach ($stmt->stmts as $function_stmt) {
if ($function_stmt instanceof PhpParser\Node\Stmt\Expression
&& $function_stmt->expr instanceof PhpParser\Node\Expr\Assign
&& $function_stmt->expr->var instanceof PhpParser\Node\Expr\PropertyFetch
&& $function_stmt->expr->var->var instanceof PhpParser\Node\Expr\Variable
&& $function_stmt->expr->var->var->name === 'this'
&& $function_stmt->expr->var->name instanceof PhpParser\Node\Identifier
&& ($property_name = $function_stmt->expr->var->name->name)
&& isset($class_storage->properties[$property_name])
&& $function_stmt->expr->expr instanceof PhpParser\Node\Expr\Variable
&& is_string($function_stmt->expr->expr->name)
&& ($param_name = $function_stmt->expr->expr->name)
&& array_key_exists($param_name, $storage->param_types)
) {
if ($class_storage->properties[$property_name]->type
|| !isset($storage->param_types[$param_name])
) {
continue;
}
$param_index = \array_search($param_name, \array_keys($storage->param_types), true);
if ($param_index === false || !isset($storage->params[$param_index]->type)) {
continue;
}
$param_type = $storage->params[$param_index]->type;
$assigned_properties[$property_name] =
$storage->params[$param_index]->is_variadic
? new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$param_type,
]),
])
: $param_type;
} else {
$assigned_properties = [];
break;
}
}
foreach ($assigned_properties as $property_name => $property_type) {
$class_storage->properties[$property_name]->type = clone $property_type;
}
}
$doc_comment = $stmt->getDocComment();
if (!$doc_comment) {
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod
&& $stmt->name->name === '__construct'
&& $class_storage
&& $storage->params
&& $this->config->infer_property_types_from_constructor
) {
$this->inferPropertyTypeFromConstructor($stmt, $storage, $class_storage);
}
return $storage;
}
@ -2459,9 +2411,77 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
$storage->return_type_description = $docblock_info->return_type_description;
}
if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod
&& $stmt->name->name === '__construct'
&& $class_storage
&& $storage->params
&& $this->config->infer_property_types_from_constructor
) {
$this->inferPropertyTypeFromConstructor($stmt, $storage, $class_storage);
}
return $storage;
}
private function inferPropertyTypeFromConstructor(
PhpParser\Node\Stmt\ClassMethod $stmt,
FunctionLikeStorage $storage,
ClassLikeStorage $class_storage
) : void {
if (!$stmt->stmts) {
return;
}
$assigned_properties = [];
foreach ($stmt->stmts as $function_stmt) {
if ($function_stmt instanceof PhpParser\Node\Stmt\Expression
&& $function_stmt->expr instanceof PhpParser\Node\Expr\Assign
&& $function_stmt->expr->var instanceof PhpParser\Node\Expr\PropertyFetch
&& $function_stmt->expr->var->var instanceof PhpParser\Node\Expr\Variable
&& $function_stmt->expr->var->var->name === 'this'
&& $function_stmt->expr->var->name instanceof PhpParser\Node\Identifier
&& ($property_name = $function_stmt->expr->var->name->name)
&& isset($class_storage->properties[$property_name])
&& $function_stmt->expr->expr instanceof PhpParser\Node\Expr\Variable
&& is_string($function_stmt->expr->expr->name)
&& ($param_name = $function_stmt->expr->expr->name)
&& array_key_exists($param_name, $storage->param_types)
) {
if ($class_storage->properties[$property_name]->type
|| !isset($storage->param_types[$param_name])
) {
continue;
}
$param_index = \array_search($param_name, \array_keys($storage->param_types), true);
if ($param_index === false || !isset($storage->params[$param_index]->type)) {
continue;
}
$param_type = $storage->params[$param_index]->type;
$assigned_properties[$property_name] =
$storage->params[$param_index]->is_variadic
? new Type\Union([
new Type\Atomic\TArray([
Type::getInt(),
$param_type,
]),
])
: $param_type;
} else {
$assigned_properties = [];
break;
}
}
foreach ($assigned_properties as $property_name => $property_type) {
$class_storage->properties[$property_name]->type = clone $property_type;
}
}
/**
* @return ?array<int, string>
*/

View File

@ -1724,6 +1724,22 @@ class PropertyTypeTest extends TestCase
echo $foo->s ?? "bar";
takesString($foo->s);',
],
'noMissingPropertyWhenArrayTypeProvided' => [
'<?php
class Foo {
private $bar;
/** @psalm-param array{key: string} $bar */
public function __construct(array $bar) {
$this->bar = $bar;
}
public function bar(): void {
echo $this->bar["key"];
}
}',
],
];
}