1
0
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:
Brown 2020-04-17 00:46:20 -04:00
parent 6f28ee684a
commit 35d376cbe7
6 changed files with 88 additions and 2 deletions

View File

@ -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) {

View File

@ -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(

View File

@ -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) {

View File

@ -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;

View File

@ -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;
}

View File

@ -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();'
],
];
}
}