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

Fix #5290 - improve inference of nested class-string template types

This commit is contained in:
Matt Brown 2021-02-27 01:00:05 -05:00
parent 216e500ea6
commit 44f8d71e72
3 changed files with 82 additions and 10 deletions

View File

@ -157,9 +157,16 @@ class TemplateStandinTypeReplacer
$atomic_type,
$input_type,
$input_arg_offset,
$calling_class,
$calling_function,
$template_result,
$codebase,
$statements_analyzer,
$replace,
$add_upper_bound,
$depth,
$was_single
$was_single,
$had_template
);
}
}
@ -806,12 +813,17 @@ class TemplateStandinTypeReplacer
Atomic\TTemplateParamClass $atomic_type,
?Union $input_type,
?int $input_arg_offset,
?string $calling_class,
?string $calling_function,
TemplateResult $template_result,
?Codebase $codebase,
?StatementsAnalyzer $statements_analyzer,
bool $replace,
bool $add_upper_bound,
int $depth,
bool $was_single
bool $was_single,
bool &$had_template
) : array {
$class_string = new Atomic\TClassString($atomic_type->as, $atomic_type->as_type);
$atomic_types = [];
if ($input_type && !$template_result->readonly) {
@ -856,6 +868,34 @@ class TemplateStandinTypeReplacer
$generic_param = \Psalm\Type::getMixed();
}
if ($atomic_type->as_type) {
// sometimes templated class-strings can contain nested templates
// in the as type that need to be resolved as well.
$as_type_union = self::replace(
new Union([$atomic_type->as_type]),
$template_result,
$codebase,
$statements_analyzer,
$generic_param,
$input_arg_offset,
$calling_class,
$calling_function,
$replace,
$add_upper_bound,
$depth + 1
);
$as_type_union_types = $as_type_union->getAtomicTypes();
$first = \reset($as_type_union_types);
if (count($as_type_union_types) === 1 && $first instanceof Atomic\TNamedObject) {
$atomic_type->as_type = $first;
} else {
$atomic_type->as_type = null;
}
}
if ($generic_param) {
if (isset($template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class])) {
$template_result->upper_bounds[$atomic_type->param_name][$atomic_type->defining_class] = new TemplateBound(
@ -878,18 +918,20 @@ class TemplateStandinTypeReplacer
[$atomic_type->param_name]
[$atomic_type->defining_class];
foreach ($template_type->getAtomicTypes() as $atomic_type) {
if ($atomic_type instanceof Atomic\TNamedObject) {
foreach ($template_type->getAtomicTypes() as $template_atomic_type) {
if ($template_atomic_type instanceof Atomic\TNamedObject) {
$atomic_types[] = new Atomic\TClassString(
$atomic_type->value,
$atomic_type
$template_atomic_type->value,
$template_atomic_type
);
} elseif ($atomic_type instanceof Atomic\TObject) {
} elseif ($template_atomic_type instanceof Atomic\TObject) {
$atomic_types[] = new Atomic\TClassString();
}
}
}
$class_string = new Atomic\TClassString($atomic_type->as, $atomic_type->as_type);
if (!$atomic_types) {
$atomic_types[] = $class_string;
}

View File

@ -40,7 +40,8 @@ class TTemplateParamClass extends TClassString
public function getId(bool $nested = false): string
{
return 'class-string<' . $this->param_name . ':' . $this->defining_class . ' as ' . $this->as . '>';
return 'class-string<' . $this->param_name . ':' . $this->defining_class
. ' as ' . ($this->as_type ? $this->as_type->getId() : $this->as) . '>';
}
public function getAssertionString(bool $exact = false): string

View File

@ -78,6 +78,35 @@ class NestedClassTemplateTest extends TestCase
return $wrapper->unwrap();
}'
],
'unwrapFromTemplatedClassString' => [
'<?php
/**
* @template TInner
*/
interface Wrapper {
/** @return TInner */
public function unwrap();
}
/**
* @extends Wrapper<string>
*/
interface StringWrapper extends Wrapper {}
/**
* @template TInner
* @template TWrapper of Wrapper<TInner>
*
* @param class-string<TWrapper> $class
* @return TInner
*/
function load(string $class) {
$package = new $class();
return $package->unwrap();
}
$result = load(StringWrapper::class);'
],
];
}