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:
parent
6b0b86b0d9
commit
783f028f70
@ -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),
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
||||
|
@ -217,6 +217,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $overridden_method_ids = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public $documenting_method_ids = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string>>
|
||||
*/
|
||||
|
@ -54,4 +54,9 @@ class MethodStorage extends FunctionLikeStorage
|
||||
* @var string
|
||||
*/
|
||||
public $defining_fqcln;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $has_docblock_param_types = false;
|
||||
}
|
||||
|
@ -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'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user