mirror of
https://github.com/danog/psalm.git
synced 2024-11-30 04:39:00 +01:00
Merge pull request #10191 from boesing/bugfix/inherited-conditional-return-types
Resolve inherited conditional return types
This commit is contained in:
commit
f57088646b
@ -183,6 +183,7 @@ class MethodCallReturnTypeFetcher
|
||||
$self_fq_class_name,
|
||||
$statements_analyzer,
|
||||
$args,
|
||||
$template_result,
|
||||
);
|
||||
|
||||
if ($return_type_candidate) {
|
||||
@ -449,7 +450,7 @@ class MethodCallReturnTypeFetcher
|
||||
$stmt_var_type = $context->vars_in_scope[$var_id]->setParentNodes(
|
||||
$var_nodes,
|
||||
);
|
||||
|
||||
|
||||
$context->vars_in_scope[$var_id] = $stmt_var_type;
|
||||
} else {
|
||||
$method_call_node = DataFlowNode::getForMethodReturn(
|
||||
|
@ -557,7 +557,8 @@ class Methods
|
||||
MethodIdentifier $method_id,
|
||||
?string &$self_class,
|
||||
?SourceAnalyzer $source_analyzer = null,
|
||||
?array $args = null
|
||||
?array $args = null,
|
||||
?TemplateResult $template_result = null
|
||||
): ?Union {
|
||||
$original_fq_class_name = $method_id->fq_class_name;
|
||||
$original_method_name = $method_id->method_name;
|
||||
@ -784,9 +785,18 @@ class Methods
|
||||
);
|
||||
|
||||
if ($found_generic_params) {
|
||||
$passed_template_result = $template_result;
|
||||
$template_result = new TemplateResult(
|
||||
[],
|
||||
$found_generic_params,
|
||||
);
|
||||
if ($passed_template_result !== null) {
|
||||
$template_result = $template_result->merge($passed_template_result);
|
||||
}
|
||||
|
||||
$overridden_storage_return_type = TemplateInferredTypeReplacer::replace(
|
||||
$overridden_storage_return_type,
|
||||
new TemplateResult([], $found_generic_params),
|
||||
$template_result,
|
||||
$source_analyzer->getCodebase(),
|
||||
);
|
||||
}
|
||||
|
@ -4,6 +4,9 @@ namespace Psalm\Internal\Type;
|
||||
|
||||
use Psalm\Type\Union;
|
||||
|
||||
use function array_merge;
|
||||
use function array_replace_recursive;
|
||||
|
||||
/**
|
||||
* This class captures the result of running Psalm's argument analysis with
|
||||
* regard to generic parameters.
|
||||
@ -63,4 +66,19 @@ class TemplateResult
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function merge(TemplateResult $result): TemplateResult
|
||||
{
|
||||
if ($result === $this) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$instance = clone $this;
|
||||
/** @var array<string, array<string, non-empty-list<TemplateBound>>> $lower_bounds */
|
||||
$lower_bounds = array_replace_recursive($instance->lower_bounds, $result->lower_bounds);
|
||||
$instance->lower_bounds = $lower_bounds;
|
||||
$instance->template_types = array_merge($instance->template_types, $result->template_types);
|
||||
|
||||
return $instance;
|
||||
}
|
||||
}
|
||||
|
@ -885,6 +885,72 @@ class ConditionalReturnTypeTest extends TestCase
|
||||
'ignored_issues' => [],
|
||||
'php_version' => '7.2',
|
||||
],
|
||||
'ineritedConditionalTemplatedReturnType' => [
|
||||
'code' => '<?php
|
||||
/** @template InstanceType */
|
||||
interface ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @template TRequestedInstance extends InstanceType
|
||||
* @param class-string<TRequestedInstance>|string $name
|
||||
* @return ($name is class-string ? TRequestedInstance : InstanceType)
|
||||
*/
|
||||
public function build(string $name): mixed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template InstanceType
|
||||
* @template-implements ContainerInterface<InstanceType>
|
||||
*/
|
||||
abstract class MixedContainer implements ContainerInterface
|
||||
{
|
||||
/** @param InstanceType $instance */
|
||||
public function __construct(private readonly mixed $instance)
|
||||
{}
|
||||
|
||||
public function build(string $name): mixed
|
||||
{
|
||||
return $this->instance;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template InstanceType of object
|
||||
* @template-extends MixedContainer<InstanceType>
|
||||
*/
|
||||
abstract class ObjectContainer extends MixedContainer
|
||||
{
|
||||
public function build(string $name): object
|
||||
{
|
||||
return parent::build($name);
|
||||
}
|
||||
}
|
||||
|
||||
/** @template-extends ObjectContainer<stdClass> */
|
||||
final class SpecificObjectContainer extends ObjectContainer
|
||||
{
|
||||
}
|
||||
|
||||
final class SpecificObject extends stdClass {}
|
||||
|
||||
$container = new SpecificObjectContainer(new stdClass());
|
||||
$object = $container->build(SpecificObject::class);
|
||||
$nonSpecificObject = $container->build("whatever");
|
||||
|
||||
/** @var ObjectContainer<object> $container */
|
||||
$container = null;
|
||||
$justObject = $container->build("whatever");
|
||||
$specificObject = $container->build(stdClass::class);
|
||||
',
|
||||
'assertions' => [
|
||||
'$object===' => 'SpecificObject',
|
||||
'$nonSpecificObject===' => 'stdClass',
|
||||
'$justObject===' => 'object',
|
||||
'$specificObject===' => 'stdClass',
|
||||
],
|
||||
'ignored_issues' => [],
|
||||
'php_version' => '8.1',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user