mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Fix #1232 - allow checking of extended templated function returns
This commit is contained in:
parent
006801f661
commit
0d52dc4e00
@ -218,17 +218,20 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer implements Statements
|
||||
}
|
||||
}
|
||||
|
||||
MethodAnalyzer::compareMethods(
|
||||
$codebase,
|
||||
$class_storage,
|
||||
$parent_storage,
|
||||
$storage,
|
||||
$parent_method_storage,
|
||||
$fq_class_name,
|
||||
$implementer_visibility,
|
||||
$codeLocation,
|
||||
$storage->suppressed_issues
|
||||
);
|
||||
// we've already checked this in the class checker
|
||||
if (!isset($class_storage->class_implements[strtolower($overridden_fq_class_name)])) {
|
||||
MethodAnalyzer::compareMethods(
|
||||
$codebase,
|
||||
$class_storage,
|
||||
$parent_storage,
|
||||
$storage,
|
||||
$parent_method_storage,
|
||||
$fq_class_name,
|
||||
$implementer_visibility,
|
||||
$codeLocation,
|
||||
$storage->suppressed_issues
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($parent_method_storage->params as $i => $guide_param) {
|
||||
if ($guide_param->type
|
||||
|
@ -544,8 +544,10 @@ class MethodAnalyzer extends FunctionLikeAnalyzer
|
||||
|
||||
if ($guide_method_storage->return_type
|
||||
&& $implementer_method_storage->return_type
|
||||
&& ($guide_method_storage->signature_return_type !== $guide_method_storage->return_type
|
||||
|| $implementer_method_storage->signature_return_type !== $implementer_method_storage->return_type)
|
||||
&& $implementer_classlike_storage->user_defined
|
||||
&& !$guide_classlike_storage->stubbed
|
||||
&& (!$guide_classlike_storage->stubbed || $guide_classlike_storage->template_types)
|
||||
) {
|
||||
$implementer_method_storage_return_type = ExpressionAnalyzer::fleshOutType(
|
||||
$codebase,
|
||||
|
@ -1226,9 +1226,16 @@ class TypeAnalyzer
|
||||
}
|
||||
|
||||
if (!$input_type_part instanceof TGenericObject) {
|
||||
$type_coerced = true;
|
||||
$type_coerced_from_mixed = true;
|
||||
return false;
|
||||
if ($input_type_part instanceof TNamedObject) {
|
||||
$input_type_part = new TGenericObject(
|
||||
$input_type_part->value,
|
||||
array_fill(0, count($container_type_part->type_params), Type::getMixed())
|
||||
);
|
||||
} else {
|
||||
$type_coerced = true;
|
||||
$type_coerced_from_mixed = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,8 +189,8 @@ class Methods
|
||||
*/
|
||||
public function getMethodParams($method_id)
|
||||
{
|
||||
if ($method_id = $this->getDeclaringMethodId($method_id)) {
|
||||
$storage = $this->getStorage($method_id);
|
||||
if ($declaring_method_id = $this->getDeclaringMethodId($method_id)) {
|
||||
$storage = $this->getStorage($declaring_method_id);
|
||||
|
||||
if ($storage->inheritdoc) {
|
||||
$non_null_param_types = array_filter(
|
||||
@ -216,7 +216,7 @@ class Methods
|
||||
return $params;
|
||||
}
|
||||
|
||||
$appearing_method_id = $this->getAppearingMethodId($method_id);
|
||||
$appearing_method_id = $this->getAppearingMethodId($declaring_method_id);
|
||||
|
||||
if (!$appearing_method_id) {
|
||||
return $params;
|
||||
|
@ -407,6 +407,8 @@ class Populator
|
||||
$storage->pseudo_property_get_types += $parent_storage->pseudo_property_get_types;
|
||||
$storage->pseudo_property_set_types += $parent_storage->pseudo_property_set_types;
|
||||
|
||||
$parent_storage->dependent_classlikes[strtolower($storage->name)] = true;
|
||||
|
||||
$storage->pseudo_methods += $parent_storage->pseudo_methods;
|
||||
}
|
||||
}
|
||||
@ -503,8 +505,8 @@ class Populator
|
||||
$implemented_interface_storage->invalid_dependencies
|
||||
);
|
||||
|
||||
if ($implemented_interface_storage->template_types
|
||||
&& isset($storage->template_type_extends[$implemented_interface_lc])
|
||||
if (isset($storage->template_type_extends[$implemented_interface_lc])
|
||||
&& $implemented_interface_storage->template_types
|
||||
) {
|
||||
foreach ($storage->template_type_extends[$implemented_interface_lc] as $i => $type) {
|
||||
$parent_template_type_names = array_keys($implemented_interface_storage->template_types);
|
||||
@ -524,16 +526,18 @@ class Populator
|
||||
|
||||
$interface_method_implementers = [];
|
||||
|
||||
foreach ($storage->class_implements as $implemented_interface) {
|
||||
foreach ($storage->class_implements as $implemented_interface_lc => $_) {
|
||||
try {
|
||||
$implemented_interface = $this->classlikes->getUnAliasedName(
|
||||
strtolower($implemented_interface)
|
||||
$implemented_interface_lc
|
||||
);
|
||||
$implemented_interface_storage = $storage_provider->get($implemented_interface);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$implemented_interface_storage->dependent_classlikes[strtolower($storage->name)] = true;
|
||||
|
||||
foreach ($implemented_interface_storage->methods as $method_name => $method) {
|
||||
if ($method->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC) {
|
||||
$mentioned_method_id = $implemented_interface . '::' . $method_name;
|
||||
|
@ -112,6 +112,14 @@ function array_flip(array $arr) {}
|
||||
*/
|
||||
function key($arr) {}
|
||||
|
||||
/**
|
||||
* @psalm-template TValue
|
||||
*
|
||||
* @param TValue $value
|
||||
* @return array<int, TValue>
|
||||
*/
|
||||
function array_fill( int $start_index, int $num, $value) : array {}
|
||||
|
||||
/**
|
||||
* @psalm-template T
|
||||
*
|
||||
|
@ -688,6 +688,13 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
// we're overwriting some methods
|
||||
$storage = $duplicate_storage;
|
||||
$this->codebase->classlike_storage_provider->makeNew(strtolower($fq_classlike_name));
|
||||
$storage->populated = false;
|
||||
|
||||
foreach ($storage->dependent_classlikes as $dependent_name_lc => $_) {
|
||||
$dependent_storage = $this->codebase->classlike_storage_provider->get($dependent_name_lc);
|
||||
$dependent_storage->populated = false;
|
||||
$this->codebase->classlike_storage_provider->makeNew($dependent_name_lc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -290,6 +290,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $invalid_dependencies = [];
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
public $dependent_classlikes = [];
|
||||
|
||||
/**
|
||||
* A hash of the source file's name, contents, and this file's modified on date
|
||||
*
|
||||
|
@ -388,6 +388,14 @@ class MethodSignatureTest extends TestCase
|
||||
|
||||
(new Y())->boo(new A());',
|
||||
],
|
||||
'allowMixedExtensionOfIteratorAggregate' => [
|
||||
'<?php
|
||||
class C implements IteratorAggregate {
|
||||
public function getIterator(): Iterator {
|
||||
return new ArrayIterator([]);
|
||||
}
|
||||
}'
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -2603,6 +2603,26 @@ class TemplateTest extends TestCase
|
||||
echo foo("hello", []);',
|
||||
'error_message' => 'PossiblyInvalidArgument',
|
||||
],
|
||||
'mismatchingTypesAfterExtends' => [
|
||||
'<?php
|
||||
class Foo {}
|
||||
class Bar {}
|
||||
|
||||
/**
|
||||
* @extends IteratorAggregate<int, Foo>
|
||||
*/
|
||||
class SomeIterator implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @return Traversable<int, Bar>
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
yield new Bar;
|
||||
}
|
||||
}',
|
||||
'error_message' => 'ImplementedReturnTypeMismatch',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user