diff --git a/src/Analyser/Generics/TemplateArgumentSolver.php b/src/Analyser/Generics/TemplateArgumentSolver.php index 25fcb53900b..310d7d426c6 100644 --- a/src/Analyser/Generics/TemplateArgumentSolver.php +++ b/src/Analyser/Generics/TemplateArgumentSolver.php @@ -131,6 +131,18 @@ private function removeSelfBounds(Type $type, string $key): Type return $type; } + private function refersToKey(Type $type, string $key): bool + { + $refers = false; + TypeTraverser::map($type, function (Type $type, callable $traverse) use ($key, &$refers): Type { + if ($type instanceof UnresolvedTemplateArgumentType && $this->representative(self::key($type->getSite(), $type->getTemplateName())) === $key) { + $refers = true; + } + return $refers ? $type : $traverse($type); + }); + return $refers; + } + /** @var array */ private array $resolving = []; @@ -207,7 +219,14 @@ private function resolveObservation(array $observation): Type $initial = $observation['initial'] !== null ? $this->substituteResolutions($observation['initial']) : null; $template = $observation['marker']->getTemplate(); $lowerBounds = []; + $key = $this->representative(self::key($observation['marker']->getSite(), $observation['marker']->getTemplateName())); foreach ($observation['lowerBounds'] as $lowerBound) { + if ($this->refersToKey($lowerBound, $key)) { + // the object passed to its own method ($map->set($map) with + // set(TOwner $owner)) can only be bounded by itself - it says + // nothing about the argument + continue; + } $inferred = $template->inferTemplateTypes($this->substituteResolutions($lowerBound))->getType($template->getName()); if ($inferred === null) { continue; diff --git a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php index e03a97fc739..25765ed7ee4 100644 --- a/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php +++ b/tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php @@ -4560,6 +4560,14 @@ public function testBug15251(): void $this->analyse([__DIR__ . '/data/bug-15251.php'], []); } + public function testBug15434(): void + { + $this->checkThisOnly = false; + $this->checkNullables = true; + $this->checkUnionTypes = true; + $this->analyse([__DIR__ . '/data/bug-15434.php'], []); + } + #[RequiresPhp('>= 8.1.0')] public function testClassConstantWildcardArguments(): void { diff --git a/tests/PHPStan/Rules/Methods/data/bug-15434.php b/tests/PHPStan/Rules/Methods/data/bug-15434.php new file mode 100644 index 00000000000..1aa4d33de20 --- /dev/null +++ b/tests/PHPStan/Rules/Methods/data/bug-15434.php @@ -0,0 +1,23 @@ +set($map); + } +} + +/** + * @template TOwner of object + */ +class WeakAnalysingMap +{ + /** + * @param TOwner $owner + */ + public function set(object $owner): void {} +} \ No newline at end of file