1
0
mirror of https://github.com/danog/psalm.git synced 2024-11-30 04:39:00 +01:00

Fix #1498 - use template types from parent function

This commit is contained in:
Brown 2019-03-25 11:25:43 -04:00
parent 6b0b86b0d9
commit 783f028f70
7 changed files with 166 additions and 86 deletions

View File

@ -274,6 +274,19 @@ class CallAnalyzer
$method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];
if ($declaring_class_storage->user_defined
&& !$method_storage->has_docblock_param_types
&& isset($declaring_class_storage->documenting_method_ids[$method_name])
) {
$documenting_method_id = $declaring_class_storage->documenting_method_ids[$method_name];
$documenting_method_storage = $codebase->methods->getStorage($documenting_method_id);
if ($documenting_method_storage->template_types) {
$method_storage = $documenting_method_storage;
}
}
if ($context->collect_exceptions) {
$context->possibly_thrown_exceptions += array_fill_keys(
array_keys($method_storage->throws),

View File

@ -245,17 +245,9 @@ class Methods
if ($declaring_method_id = $this->getDeclaringMethodId($method_id)) {
$storage = $this->getStorage($declaring_method_id);
$non_null_param_types = array_filter(
$storage->params,
/** @return bool */
function (FunctionLikeParameter $p) {
return $p->type !== null && $p->has_docblock_type;
}
);
$params = $storage->params;
if ($non_null_param_types) {
if ($storage->has_docblock_param_types) {
return $params;
}
@ -273,49 +265,40 @@ class Methods
return $params;
}
foreach ($class_storage->overridden_method_ids[$appearing_method_name] as $overridden_method_id) {
$overridden_storage = $this->getStorage($overridden_method_id);
if (!isset($class_storage->documenting_method_ids[$appearing_method_name])) {
return $params;
}
list($overriding_fq_class_name) = explode('::', $overridden_method_id);
$overridden_method_id = $class_storage->documenting_method_ids[$appearing_method_name];
$overridden_storage = $this->getStorage($overridden_method_id);
$non_null_param_types = array_filter(
$overridden_storage->params,
/** @return bool */
function (FunctionLikeParameter $p) {
return $p->type !== null && $p->has_docblock_type;
}
);
list($overriding_fq_class_name) = explode('::', $overridden_method_id);
if ($non_null_param_types) {
foreach ($params as $i => $param) {
if (isset($overridden_storage->params[$i]->type)
&& $overridden_storage->params[$i]->has_docblock_type
&& $overridden_storage->params[$i]->name === $param->name
) {
$params[$i] = clone $param;
/** @var Type\Union $params[$i]->type */
$params[$i]->type = clone $overridden_storage->params[$i]->type;
foreach ($params as $i => $param) {
if (isset($overridden_storage->params[$i]->type)
&& $overridden_storage->params[$i]->has_docblock_type
&& $overridden_storage->params[$i]->name === $param->name
) {
$params[$i] = clone $param;
/** @var Type\Union $params[$i]->type */
$params[$i]->type = clone $overridden_storage->params[$i]->type;
if ($source) {
$params[$i]->type = self::localizeParamType(
$source->getCodebase(),
$params[$i]->type,
$appearing_fq_class_name,
$overriding_fq_class_name
);
}
if ($params[$i]->signature_type
&& $params[$i]->signature_type->isNullable()
) {
$params[$i]->type->addType(new Type\Atomic\TNull);
}
$params[$i]->type_location = $overridden_storage->params[$i]->type_location;
}
if ($source) {
$params[$i]->type = self::localizeParamType(
$source->getCodebase(),
$params[$i]->type,
$appearing_fq_class_name,
$overriding_fq_class_name
);
}
break;
if ($params[$i]->signature_type
&& $params[$i]->signature_type->isNullable()
) {
$params[$i]->type->addType(new Type\Atomic\TNull);
}
$params[$i]->type_location = $overridden_storage->params[$i]->type_location;
}
}

View File

@ -272,6 +272,53 @@ class Populator
}
}
foreach ($storage->methods as $method_name => $method_storage) {
if (isset($storage->overridden_method_ids[$method_name])) {
foreach ($storage->overridden_method_ids[$method_name] as $declaring_method_id) {
list($declaring_class, $declaring_method_name) = explode('::', $declaring_method_id);
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_class);
$declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];
if ($declaring_method_storage->has_docblock_param_types
&& !$method_storage->has_docblock_param_types
&& !isset($storage->documenting_method_ids[$method_name])
) {
$storage->documenting_method_ids[$method_name] = $declaring_method_id;
}
// tell the declaring class it's overridden downstream
$declaring_method_storage->overridden_downstream = true;
$declaring_method_storage->overridden_somewhere = true;
if (!$method_storage->throws
&& $method_storage->inheritdoc
&& $declaring_method_storage->throws
) {
$method_storage->throws = $declaring_method_storage->throws;
}
if (count($storage->overridden_method_ids[$method_name]) === 1
&& $method_storage->signature_return_type
&& !$method_storage->signature_return_type->isVoid()
&& $method_storage->return_type === $method_storage->signature_return_type
) {
if (isset($declaring_class_storage->methods[$method_name])) {
$declaring_method_storage = $declaring_class_storage->methods[$method_name];
if ($declaring_method_storage->return_type
&& $declaring_method_storage->signature_return_type
&& $declaring_method_storage->return_type
!== $declaring_method_storage->signature_return_type
) {
$method_storage->return_type = $declaring_method_storage->return_type;
}
}
}
}
}
}
if ($this->debug_output) {
echo 'Have populated ' . $storage->name . "\n";
}
@ -981,46 +1028,6 @@ class Populator
$storage->inheritable_method_ids[$aliased_method_name] = $declaring_method_id;
}
}
foreach ($storage->methods as $method_name => $method_storage) {
if (isset($storage->overridden_method_ids[$method_name])) {
foreach ($storage->overridden_method_ids[$method_name] as $declaring_method_id) {
list($declaring_class, $declaring_method_name) = explode('::', $declaring_method_id);
$declaring_class_storage = $this->classlike_storage_provider->get($declaring_class);
$declaring_method_storage = $declaring_class_storage->methods[strtolower($declaring_method_name)];
// tell the declaring class it's overridden downstream
$declaring_method_storage->overridden_downstream = true;
$declaring_method_storage->overridden_somewhere = true;
if (!$method_storage->throws
&& $method_storage->inheritdoc
&& $declaring_method_storage->throws
) {
$method_storage->throws = $declaring_method_storage->throws;
}
if (count($storage->overridden_method_ids[$method_name]) === 1
&& $method_storage->signature_return_type
&& !$method_storage->signature_return_type->isVoid()
&& $method_storage->return_type === $method_storage->signature_return_type
) {
if (isset($declaring_class_storage->methods[$method_name])) {
$declaring_method_storage = $declaring_class_storage->methods[$method_name];
if ($declaring_method_storage->return_type
&& $declaring_method_storage->signature_return_type
&& $declaring_method_storage->return_type
!== $declaring_method_storage->signature_return_type
) {
$method_storage->return_type = $declaring_method_storage->return_type;
}
}
}
}
}
}
}
/**

View File

@ -2019,6 +2019,17 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
);
}
if ($storage instanceof MethodStorage) {
$storage->has_docblock_param_types = (bool) array_filter(
$storage->params,
/** @return bool */
function (FunctionLikeParameter $p) {
return $p->type !== null && $p->has_docblock_type;
}
);
}
foreach ($docblock_info->params_out as $docblock_param_out) {
$param_name = substr($docblock_param_out['name'], 1);

View File

@ -217,6 +217,11 @@ class ClassLikeStorage
*/
public $overridden_method_ids = [];
/**
* @var array<string, string>
*/
public $documenting_method_ids = [];
/**
* @var array<string, array<string>>
*/

View File

@ -54,4 +54,9 @@ class MethodStorage extends FunctionLikeStorage
* @var string
*/
public $defining_fqcln;
/**
* @var bool
*/
public $has_docblock_param_types = false;
}

View File

@ -1657,6 +1657,27 @@ class TemplateExtendsTest extends TestCase
}
}',
],
'templateNotExtendedButSignatureInherited' => [
'<?php
class Base {
/**
* @template T
* @param T $x
* @return T
*/
function example($x) {
return $x;
}
}
class Child extends Base {
function example($x) {
return $x;
}
}
ord((new Child())->example("str"));'
],
];
}
@ -2384,6 +2405,41 @@ class TemplateExtendsTest extends TestCase
}',
'error_message' => 'InvalidReturnType',
],
'implementsChildClassWithNonExtendedTemplate' => [
'<?php
/**
* @template T
*/
class Base {
/** @var T */
private $t;
/** @param T $t */
public function __construct($t) {
$this->t = $t;
}
/**
* @param T $x
* @return T
*/
function example($x) {
return $x;
}
}
class Child extends Base {
function example($x) {
return $x;
}
}
/** @param Child $c */
function bar(Child $c) : void {
ord($c->example("boris"));
}',
'error_message' => 'MixedArgument - src/somefile.php:31:29 - Argument 1 of ord cannot be mixed, expecting string'
],
];
}
}