mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Fix #3164 - allow nested templates in conditional classes
This commit is contained in:
parent
6f28ee684a
commit
35d376cbe7
@ -981,6 +981,15 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($param_type->hasTemplate() && $param_type->isSingle()) {
|
||||
/** @var Type\Atomic\TTemplateParam */
|
||||
$template_type = \array_values($param_type->getAtomicTypes())[0];
|
||||
|
||||
if ($template_type->as->getTemplateTypes()) {
|
||||
$param_type = $template_type->as;
|
||||
}
|
||||
}
|
||||
|
||||
$var_type = $param_type;
|
||||
|
||||
if ($function_param->is_variadic) {
|
||||
|
@ -608,6 +608,18 @@ class NegatedAssertionReconciler extends Reconciler
|
||||
$existing_var_type->removeType('null');
|
||||
}
|
||||
|
||||
foreach ($existing_var_type->getAtomicTypes() as $type) {
|
||||
if ($type instanceof TTemplateParam) {
|
||||
if ($type->as->hasType('null') && !$type->as->isNull()) {
|
||||
$type->as->removeType('null');
|
||||
|
||||
$did_remove_type = true;
|
||||
|
||||
$existing_var_type->bustCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$did_remove_type || empty($existing_var_type->getAtomicTypes())) {
|
||||
if ($key && $code_location && !$is_equality) {
|
||||
self::triggerIssueForImpossible(
|
||||
|
@ -121,6 +121,7 @@ class UnionTemplateHandler
|
||||
$calling_function,
|
||||
$template_result,
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$replace,
|
||||
$add_upper_bound,
|
||||
$depth,
|
||||
@ -390,6 +391,7 @@ class UnionTemplateHandler
|
||||
?string $calling_function,
|
||||
TemplateResult $template_result,
|
||||
?Codebase $codebase,
|
||||
?StatementsAnalyzer $statements_analyzer,
|
||||
bool $replace,
|
||||
bool $add_upper_bound,
|
||||
int $depth,
|
||||
@ -494,6 +496,20 @@ class UnionTemplateHandler
|
||||
)
|
||||
)
|
||||
) {
|
||||
$atomic_type->as = self::replaceTemplateTypesWithStandins(
|
||||
$atomic_type->as,
|
||||
$template_result,
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$input_type,
|
||||
$input_arg_offset,
|
||||
$calling_class,
|
||||
$calling_function,
|
||||
$replace,
|
||||
$add_upper_bound,
|
||||
$depth + 1
|
||||
);
|
||||
|
||||
$generic_param = clone $input_type;
|
||||
|
||||
if ($matching_input_keys) {
|
||||
|
@ -2,8 +2,10 @@
|
||||
namespace Psalm\Internal\TypeVisitor;
|
||||
|
||||
use Psalm\Type\Atomic\TTemplateParam;
|
||||
use Psalm\Type\Atomic\TTemplateParamClass;
|
||||
use Psalm\Type\TypeNode;
|
||||
use Psalm\Type\NodeVisitor;
|
||||
use Psalm\Type\Union;
|
||||
|
||||
class TemplateTypeCollector extends NodeVisitor
|
||||
{
|
||||
@ -16,6 +18,14 @@ class TemplateTypeCollector extends NodeVisitor
|
||||
{
|
||||
if ($type instanceof TTemplateParam) {
|
||||
$this->template_types[] = $type;
|
||||
} elseif ($type instanceof TTemplateParamClass) {
|
||||
$extends = $type->as_type;
|
||||
|
||||
$this->template_types[] = new TTemplateParam(
|
||||
$type->param_name,
|
||||
$extends ? new Union([$extends]) : \Psalm\Type::getMixed(),
|
||||
$type->defining_class
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -1265,7 +1265,11 @@ abstract class Type
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($type_tokens[$i + 1]) && $type_tokens[$i + 1][0] === ':') {
|
||||
if (isset($type_tokens[$i + 1])
|
||||
&& $type_tokens[$i + 1][0] === ':'
|
||||
&& isset($type_tokens[$i - 1])
|
||||
&& ($type_tokens[$i - 1][0] === '{' || $type_tokens[$i - 1][0] === ',')
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -1310,8 +1314,12 @@ abstract class Type
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($type_tokens[$i + 1])) {
|
||||
if (isset($type_tokens[$i + 1])
|
||||
&& isset($type_tokens[$i - 1])
|
||||
&& ($type_tokens[$i - 1][0] === '{' || $type_tokens[$i - 1][0] === ',')
|
||||
) {
|
||||
$next_char = $type_tokens[$i + 1][0];
|
||||
|
||||
if ($next_char === ':') {
|
||||
continue;
|
||||
}
|
||||
|
@ -253,6 +253,37 @@ class ConditionalReturnTypeTest extends TestCase
|
||||
return true;
|
||||
}'
|
||||
],
|
||||
'nullableClassString' => [
|
||||
'<?php
|
||||
namespace Foo;
|
||||
|
||||
class A {
|
||||
public function test1() : void {}
|
||||
}
|
||||
|
||||
class Application {
|
||||
public function test2() : void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T of object
|
||||
* @template TName as class-string<T>|null
|
||||
*
|
||||
* @psalm-param TName $className
|
||||
*
|
||||
* @psalm-return (TName is null ? Application : T)
|
||||
*/
|
||||
function app(?string $className = null) {
|
||||
if ($className === null) {
|
||||
return new Application();
|
||||
}
|
||||
|
||||
return new $className();
|
||||
}
|
||||
|
||||
app(A::class)->test1();
|
||||
app()->test2();'
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user