Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Prev Previous commit
Next Next commit
Skip ancestor stubs with template types for non-stub built-in classes
The isBuiltin() guard allowed all built-in classes to walk ancestors for
stub PHPDocs. This caused classes like SplFileObject to pick up
Iterator::key()'s @return TKey which resolves to mixed, overriding
more specific signature map types (e.g. int).

Now the ancestor walk skips stubs whose param/return types contain
template types when the declaring class is not in the stub system.
This preserves the DateTimeInterface::diff() fix (concrete types)
while avoiding regressions for Iterator/ArrayAccess/CachingIterator.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
  • Loading branch information
phpstan-bot and claude committed May 18, 2026
commit 04c5ee133c8fb800ab925efa005fd43329e76063
22 changes: 21 additions & 1 deletion src/Reflection/Php/PhpClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypehintHelper;
use PHPStan\Type\TypeUtils;
use PHPStan\Type\UnionType;
use function array_key_exists;
use function array_keys;
Expand Down Expand Up @@ -1317,7 +1318,8 @@ private function findMethodPhpDocIncludingAncestors(
if ($resolved !== null) {
return [$resolved, $declaringClass];
}
if (!$declaringClass->isBuiltin() && !$this->stubPhpDocProvider->isKnownClass($declaringClassName)) {
$isKnownClass = $this->stubPhpDocProvider->isKnownClass($declaringClassName);
if (!$isKnownClass && !$declaringClass->isBuiltin()) {
return null;
}

Expand All @@ -1335,10 +1337,28 @@ private function findMethodPhpDocIncludingAncestors(
continue;
}

if (!$isKnownClass && $this->stubPhpDocContainsTemplateTypes($resolved)) {
continue;
}

return [$resolved, $ancestor];
}

return null;
}

private function stubPhpDocContainsTemplateTypes(ResolvedPhpDocBlock $phpDoc): bool
{
$returnTag = $phpDoc->getReturnTag();
if ($returnTag !== null && TypeUtils::containsTemplateType($returnTag->getType())) {
return true;
}
foreach ($phpDoc->getParamTags() as $paramTag) {
if (TypeUtils::containsTemplateType($paramTag->getType())) {
return true;
}
}
return false;
}

}
Loading