Improve type inference for coalesce with ErrorType, strpos === int narrowing, and str_repeat return types#5508
Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
Conversation
…t narrowing, and `str_repeat` return types - Handle ErrorType in InitializerExprTypeResolver coalesce: `[][0] ?? 42` now resolves to `42` instead of `*ERROR*` - Apply truthy narrowing when function/method calls are compared to truthy constants via ===, enabling `strpos($s, ':') === 5` to narrow `$s` to non-falsy-string - Return non-falsy-string from str_repeat when input is non-empty and multiplier >= 2 (result length >= 2 means it can't be '0' or '') - Preserve numeric-string in str_repeat when multiplier is exactly 1 - Update test assertions for all improved type inference cases
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses several "could be" type inference improvement opportunities identified in test files (phpstan/phpstan#14510). Each fix improves the precision of PHPStan's type inference for specific patterns.
Changes
1. Handle
ErrorTypein constant expression coalesce (InitializerExprTypeResolver)src/Reflection/InitializerExprTypeResolver.php??operator in a constant expression evaluates toErrorType(e.g.,[][0]— accessing an undefined offset), the coalesce should return the right side[][0] ?? 42→*ERROR*[][0] ?? 42→422. Apply truthy narrowing for function calls compared to truthy constants (
TypeSpecifier)src/Analyser/TypeSpecifier.php===to a truthy constant (non-false, non-0, non-null, non-''), this implies the function returned non-false, enabling type-specifying extensions to narrow argument typesFuncCall,MethodCall, andStaticCallexpressions to avoid interfering with other narrowing handlers (e.g.,$object::class === 'Foo')strpos($s, ':') === 5→$sstays asstringstrpos($s, ':') === 5→$snarrowed tonon-falsy-string(via StrContainingTypeSpecifyingExtension)mb_strpos,strrpos,stripos,strripos, and theirmb_variants3. Improve
str_repeatreturn type precision (StrRepeatFunctionReturnTypeExtension)src/Type/Php/StrRepeatFunctionReturnTypeExtension.php'0'(single char) or'', so it's non-falsy-stringstr_repeat('0', 100)→non-empty-stringstr_repeat('0', 100)→non-falsy-stringstr_repeat($numericString, 1)→non-empty-stringstr_repeat($numericString, 1)→non-empty-string&numeric-stringRoot cause
Each fix addresses a different gap in type inference:
Coalesce with ErrorType: The
??handler inInitializerExprTypeResolveronly removednullfrom the left type. When the left side wasErrorType(from accessing an undefined array offset),removeNull(ErrorType)returnedErrorTypeunchanged.Truthy constant comparison narrowing: The
specifyTypesForConstantBinaryExpressionmethod inTypeSpecifieronly handled=== falseand=== truecomparisons for triggering type-specifying extensions. Comparing to other truthy constants (like integers) didn't trigger the same narrowing, even though it implies the function returned non-false.str_repeat type loss: The
StrRepeatFunctionReturnTypeExtensiondidn't account for the fact that repeating any non-empty string 2+ times guarantees non-falsiness (length >= 2). It also lost the numeric-string property when the multiplier was exactly 1.Analogous cases probed
ConstantFloatType(0.0)from type narrowing ($num === 0.0) can represent -0.0 at runtime, so the union'-0'|'0'is correct for the narrowed case.%d%dnumeric-string: Investigated but the "could be" was incorrect —sprintf('%d%d', '5', '-3')produces'5-3'which is NOT numeric.strlen($nonES) >= strlen($s)narrowing to non-empty-string: Investigated but the "could be" was incorrect — if$nonEShas length 1,$scan still be empty.Test
tests/PHPStan/Analyser/nsrt/initializer-expr-type-resolver.php—*ERROR*→42for coalesce with undefined array accesstests/PHPStan/Analyser/nsrt/non-empty-string-str-containing-fns.php—string→non-falsy-stringforstrpos($s, ':') === 5andmb_strpos($s, ':') === 5tests/PHPStan/Analyser/nsrt/literal-string.php:str_repeat('0', 100):non-empty-string→non-falsy-stringstr_repeat($numericString, 100):non-empty-string→non-falsy-stringstr_repeat($numericString, 1):non-empty-string→non-empty-string&numeric-stringstr_repeat($numericString, 2):non-empty-string→non-falsy-stringFixes phpstan/phpstan#14510