From acd5f2ac1ce17a1d9821a576ba23cda141b84516 Mon Sep 17 00:00:00 2001 From: Richard van Velzen Date: Mon, 15 Aug 2022 15:04:42 +0200 Subject: [PATCH] Fix offset access + key-of template type combination --- src/Type/Generic/TemplateKeyOfType.php | 18 ++++++++++ src/Type/Generic/TemplateTypeTrait.php | 8 ++++- .../Analyser/AnalyserIntegrationTest.php | 2 +- .../Analyser/NodeScopeResolverTest.php | 1 + tests/PHPStan/Analyser/data/bug-7788.php | 34 +++++++++++++++++++ 5 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/PHPStan/Analyser/data/bug-7788.php diff --git a/src/Type/Generic/TemplateKeyOfType.php b/src/Type/Generic/TemplateKeyOfType.php index 94ccb8f27f..9532caebff 100644 --- a/src/Type/Generic/TemplateKeyOfType.php +++ b/src/Type/Generic/TemplateKeyOfType.php @@ -46,6 +46,24 @@ public function traverse(callable $cb): Type return $this; } + protected function getResult(): Type + { + $result = $this->getBound()->getResult(); + + $type = TemplateTypeFactory::create( + $this->getScope(), + $this->getName(), + $result, + $this->getVariance(), + ); + + if ($this->isArgument()) { + $type = TemplateTypeHelper::toArgument($type); + } + + return $type; + } + protected function shouldGeneralizeInferredType(): bool { return false; diff --git a/src/Type/Generic/TemplateTypeTrait.php b/src/Type/Generic/TemplateTypeTrait.php index 8b8f2322c8..0366198279 100644 --- a/src/Type/Generic/TemplateTypeTrait.php +++ b/src/Type/Generic/TemplateTypeTrait.php @@ -249,8 +249,14 @@ public function inferTemplateTypes(Type $receivedType): TemplateTypeMap $map = $this->getBound()->inferTemplateTypes($receivedType); $resolvedBound = TypeUtils::resolveLateResolvableTypes(TemplateTypeHelper::resolveTemplateTypes($this->getBound(), $map)); if ($resolvedBound->isSuperTypeOf($receivedType)->yes()) { + if ($this->shouldGeneralizeInferredType()) { + $generalizedType = $receivedType->generalize(GeneralizePrecision::templateArgument()); + if ($resolvedBound->isSuperTypeOf($generalizedType)->yes()) { + $receivedType = $generalizedType; + } + } return (new TemplateTypeMap([ - $this->name => $this->shouldGeneralizeInferredType() ? $receivedType->generalize(GeneralizePrecision::templateArgument()) : $receivedType, + $this->name => $receivedType, ]))->union($map); } diff --git a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php index 94133a4f34..b6fdb5754c 100644 --- a/tests/PHPStan/Analyser/AnalyserIntegrationTest.php +++ b/tests/PHPStan/Analyser/AnalyserIntegrationTest.php @@ -806,7 +806,7 @@ public function testBug7094(): void $this->assertSame('Return type of call to method Bug7094\Foo::getAttribute() contains unresolvable type.', $errors[4]->getMessage()); $this->assertSame(79, $errors[4]->getLine()); - $this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, non-empty-array given.', $errors[5]->getMessage()); + $this->assertSame('Parameter #1 $attr of method Bug7094\Foo::setAttributes() expects array{foo?: string, bar?: 5|6|7, baz?: bool}, non-empty-array given.', $errors[5]->getMessage()); $this->assertSame(29, $errors[5]->getLine()); } diff --git a/tests/PHPStan/Analyser/NodeScopeResolverTest.php b/tests/PHPStan/Analyser/NodeScopeResolverTest.php index bebe21244e..69b06737f2 100644 --- a/tests/PHPStan/Analyser/NodeScopeResolverTest.php +++ b/tests/PHPStan/Analyser/NodeScopeResolverTest.php @@ -977,6 +977,7 @@ public function dataFileAsserts(): iterable } yield from $this->gatherAssertTypes(__DIR__ . '/data/closure-argument-type.php'); + yield from $this->gatherAssertTypes(__DIR__ . '/data/bug-7788.php'); } /** diff --git a/tests/PHPStan/Analyser/data/bug-7788.php b/tests/PHPStan/Analyser/data/bug-7788.php new file mode 100644 index 0000000000..944d10e7e4 --- /dev/null +++ b/tests/PHPStan/Analyser/data/bug-7788.php @@ -0,0 +1,34 @@ + + */ +final class Props +{ + /** + * @param T $props + */ + public function __construct(private array $props = []) + { + } + + /** + * @template K of key-of + * @template TDefault + * @param K $propKey + * @param TDefault $default + * @return T[K]|TDefault + */ + public function getProp(string $propKey, mixed $default = null): mixed + { + return $this->props[$propKey] ?? $default; + } +} + +function () { + assertType('int', (new Props(['title' => 'test', 'value' => 30]))->getProp('value', 0)); +};