Fix phpstan/phpstan#13325: RegexIterator returns a generic type with unknown parameters#5403
Fix phpstan/phpstan#13325: RegexIterator returns a generic type with unknown parameters#5403phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
Conversation
…ameters - Fixed GenericTypeTemplateTraverser to continue traversing into bounds of unresolvable template types - When a template type (like TIterator) can't be inferred from constructor args, its bound may still contain other template types (TKey, TValue) that need resolution - Added regression test for phpstan/phpstan#13325
VincentLanglet
left a comment
There was a problem hiding this comment.
This now gives
Method pocketmine\world\format\io\region\RegionWorldProvider::createRegionIterator() should return RegexIterator<mixed, string, FilesystemIterator> but returns RegexIterator<mixed, mixed, Traversable<mixed, mixed>>.
with something like
/**
* @phpstan-return \RegexIterator<mixed, string, \FilesystemIterator>
*/
private function createRegionIterator() : \RegexIterator{
return new \RegexIterator(
new \FilesystemIterator('foo'),
'bar',
'baz'
);
}
PHPstan is not able to infer all TKey, TValue and TIterator types from new RegexIterator.
I think we should be able to write some code to be able to infer all three along with the change to @param TIterator $iterator but right now that's not the case.
…generic inference Change @param from Iterator<TKey, TValue> to TIterator in RegexIterator's constructor stub, matching the pattern already used by IteratorIterator. This allows PHPStan to infer TIterator from the actual argument type (e.g. FilesystemIterator) rather than falling back to the bound Traversable<TKey, TValue>. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Pushed. Here's a summary of the changes: Root cause: The Fix: Changed Tests:
|
Summary
When constructing a
RegexIterator, PHPStan couldn't fully resolve template types. The class has three templates:TKey,TValue, andTIterator of Traversable<TKey, TValue>. Since the constructor only acceptsIterator<TKey, TValue>,TIteratorcan't be directly inferred and falls back to its boundTraversable<TKey, TValue>. However, theTKey/TValuetemplate types within that bound were not being further resolved, leaving them as raw template type placeholders.Changes
src/Analyser/Traverser/GenericTypeTemplateTraverser.phpto call$traverse()on the resolved bound, so inner template types are recursively resolvedtests/PHPStan/Rules/Methods/data/bug-13325.phpandtests/PHPStan/Rules/Methods/ReturnTypeRuleTest.phpRoot cause
In
GenericTypeTemplateTraverser::traverse(), when a template type couldn't be resolved from the template type map (mapped toErrorType), it was replaced with its bound via$type->getDefault() ?? $type->getBound(). However, this return value was not passed through$traverse(), so any template types nested inside the bound (e.g.,TKeyandTValueinsideTraversable<TKey, TValue>) were never resolved. The fix simply wraps the return in$traverse()to continue recursive resolution.Test
Added a rule test reproducing the exact scenario from the issue: a method returning
RegexIterator<mixed, mixed, Traversable<mixed, mixed>>that createsnew RegexIterator($iterator, 'string'). Before the fix, this produced a false positive about mismatched return types.Fixes phpstan/phpstan#13325