Skip to content

Commit

Permalink
Fix #5496 - ensure params extended in properties are properly fleshed…
Browse files Browse the repository at this point in the history
… out
  • Loading branch information
muglug committed Mar 29, 2021
1 parent f41deea commit 9a714b7
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 35 deletions.
87 changes: 53 additions & 34 deletions src/Psalm/Internal/Analyzer/ClassAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Psalm\DocComment;
use Psalm\Exception\DocblockParseException;
use Psalm\Internal\Analyzer\Statements\Expression\Call\ClassTemplateParamCollector;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\AtomicPropertyFetchAnalyzer;
use Psalm\Internal\FileManipulation\PropertyDocblockManipulator;
use Psalm\Internal\Type\TemplateStandinTypeReplacer;
use Psalm\Internal\Type\Comparator\UnionTypeComparator;
Expand Down Expand Up @@ -837,20 +838,28 @@ public static function addContextProperties(
true
);

$template_result = new \Psalm\Internal\Type\TemplateResult(
$class_template_params ?: [],
[]
);

if ($class_template_params) {
$fleshed_out_type = TemplateStandinTypeReplacer::replace(
$fleshed_out_type,
$template_result,
$this_object_type = self::getThisObjectType(
$storage,
$fq_class_name
);

if (!$this_object_type instanceof Type\Atomic\TGenericObject) {
$type_params = [];

foreach ($class_template_params as $type_map) {
$type_params[] = clone \array_values($type_map)[0];
}

$this_object_type = new Type\Atomic\TGenericObject($this_object_type->value, $type_params);
}

$fleshed_out_type = AtomicPropertyFetchAnalyzer::localizePropertyType(
$codebase,
null,
null,
null,
$class_context->self
$fleshed_out_type,
$this_object_type,
$storage,
$property_class_storage
);
}

Expand Down Expand Up @@ -1796,6 +1805,34 @@ private function analyzeClassMethod(
return $method_analyzer;
}

private static function getThisObjectType(
ClassLikeStorage $class_storage,
string $original_fq_classlike_name
): Type\Atomic\TNamedObject {
if ($class_storage->template_types) {
$template_params = [];

foreach ($class_storage->template_types as $param_name => $template_map) {
$key = array_keys($template_map)[0];

$template_params[] = new Type\Union([
new Type\Atomic\TTemplateParam(
$param_name,
\reset($template_map),
$key
)
]);
}

return new Type\Atomic\TGenericObject(
$original_fq_classlike_name,
$template_params
);
}

return new Type\Atomic\TNamedObject($original_fq_classlike_name);
}

public static function analyzeClassMethodReturnType(
PhpParser\Node\Stmt\ClassMethod $stmt,
MethodAnalyzer $method_analyzer,
Expand Down Expand Up @@ -1834,28 +1871,10 @@ public static function analyzeClassMethodReturnType(
$class_storage = $codebase->classlike_storage_provider->get($declaring_class_name);
}

if ($class_storage->template_types) {
$template_params = [];

foreach ($class_storage->template_types as $param_name => $template_map) {
$key = array_keys($template_map)[0];

$template_params[] = new Type\Union([
new Type\Atomic\TTemplateParam(
$param_name,
\reset($template_map),
$key
)
]);
}

$this_object_type = new Type\Atomic\TGenericObject(
$original_fq_classlike_name,
$template_params
);
} else {
$this_object_type = new Type\Atomic\TNamedObject($original_fq_classlike_name);
}
$this_object_type = self::getThisObjectType(
$class_storage,
$original_fq_classlike_name
);

$class_template_params = ClassTemplateParamCollector::collect(
$codebase,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ class ClassTemplateParamCollector
{
/**
* @param lowercase-string $method_name
* @return array<string, array<string, Type\Union>>|null
* @return array<string, non-empty-array<string, Type\Union>>|null
* @psalm-suppress MoreSpecificReturnType
* @psalm-suppress LessSpecificReturnStatement
*/
public static function collect(
Codebase $codebase,
Expand Down
4 changes: 4 additions & 0 deletions src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,10 @@ public static function handleTemplateParamClassStandin(
bool $was_single,
bool &$had_template
) : array {
if ($atomic_type->defining_class === $calling_class) {
return [$atomic_type];
}

$atomic_types = [];

if ($input_type && !$template_result->readonly) {
Expand Down
2 changes: 2 additions & 0 deletions tests/MixinAnnotationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ abstract class FooChild extends Foo{}
/**
* @psalm-suppress MissingConstructor
* @psalm-suppress PropertyNotSetInConstructor
*/
final class FooGrandChild extends FooChild {}
Expand Down Expand Up @@ -510,6 +511,7 @@ abstract class FooChild extends Foo{}
/**
* @psalm-suppress MissingConstructor
* @psalm-suppress PropertyNotSetInConstructor
*/
final class FooGrandChild extends FooChild {}
Expand Down
26 changes: 26 additions & 0 deletions tests/Template/ClassTemplateExtendsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4400,6 +4400,32 @@ public function __construct()
[],
'7.4'
],
'extendTemplatedClassString' => [
'<?php
/** @template T1 of object */
abstract class ParentClass {
/** @var class-string<T1> */
protected $c;
/** @param class-string<T1> $c */
public function __construct(string $c) {
$this->c = $c;
}
/** @return class-string<T1> */
abstract public function foo(): string;
}
/**
* @template T2 of object
* @extends ParentClass<T2>
*/
class ChildClass extends ParentClass {
public function foo(): string {
return $this->c;
}
}'
],
];
}

Expand Down

0 comments on commit 9a714b7

Please sign in to comment.