Skip to content

Commit

Permalink
Merge pull request #10191 from boesing/bugfix/inherited-conditional-r…
Browse files Browse the repository at this point in the history
…eturn-types

Resolve inherited conditional return types
  • Loading branch information
orklah committed Sep 28, 2023
2 parents bbcf503 + eda55a2 commit f570886
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ public static function fetch(
$self_fq_class_name,
$statements_analyzer,
$args,
$template_result,
);

if ($return_type_candidate) {
Expand Down Expand Up @@ -449,7 +450,7 @@ public static function taintMethodCallResult(
$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(
Expand Down
14 changes: 12 additions & 2 deletions src/Psalm/Internal/Codebase/Methods.php
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,8 @@ public function getMethodReturnType(
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;
Expand Down Expand Up @@ -784,9 +785,18 @@ public function getMethodReturnType(
);

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(),
);
}
Expand Down
18 changes: 18 additions & 0 deletions src/Psalm/Internal/Type/TemplateResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

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.
Expand Down Expand Up @@ -63,4 +66,19 @@ public function __construct(array $template_types, array $lower_bounds)
}
}
}

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;
}
}
66 changes: 66 additions & 0 deletions tests/Template/ConditionalReturnTypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,72 @@ function getSomethingElse()
'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',
],
];
}
}

0 comments on commit f570886

Please sign in to comment.