Skip to content
Permalink
Browse files

Infer missing docblock-supplied types from constructor

Fixes #2071
  • Loading branch information...
muglug committed Aug 27, 2019
1 parent 3b865f6 commit 1cb8c3f6c42c13b388b51099f04262e48220dc3d
Showing with 93 additions and 57 deletions.
  1. +77 −57 src/Psalm/Internal/Visitor/ReflectorVisitor.php
  2. +16 −0 tests/PropertyTypeTest.php
@@ -1912,66 +1912,18 @@ private function registerFunctionLike(PhpParser\Node\FunctionLike $stmt, $fake_m
$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 @@ function (FunctionLikeParameter $p) {
$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>
*/
@@ -1724,6 +1724,22 @@ function takesString(string $_s) : void {}
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"];
}
}',
],
];
}

0 comments on commit 1cb8c3f

Please sign in to comment.
You can’t perform that action at this time.