Skip to content

Fix phpstan/phpstan#11129: concat of numeric constant string and int should lead to numeric-string#5413

Closed
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ou1v20i
Closed

Fix phpstan/phpstan#11129: concat of numeric constant string and int should lead to numeric-string#5413
phpstan-bot wants to merge 2 commits intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ou1v20i

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When a loop variable alternates between int and string types (e.g., $pos = '0' . $pos), PHPStan would degrade the variable's type to mixed after a few loop iterations. This happened because UnionType::toNumber() returned ErrorType when any union member couldn't be converted to a number, since ErrorType extends MixedType and absorbs all valid results in TypeCombinator::union(). The ErrorType then cascaded through arithmetic operations, causing the entire loop's type inference to collapse.

Changes

  • Added filterNumberTypeFromUnion() helper method in src/Reflection/InitializerExprTypeResolver.php that extracts valid numeric types from union members when toNumber() returns ErrorType
  • The filter only skips ErrorType results from string members (since string-to-number conversion always produces a result in PHP), preserving error detection for genuinely invalid types like arrays and objects
  • Applied the filter in resolveCommonMath() before the ErrorType check, so arithmetic on union types with string members no longer cascades to ErrorType
  • Removed the @phpstan-ignore binaryOp.invalid inline comment on line 1291 (no longer needed)
  • Removed 3 baseline entries for +, -, * operations on bool|float|int|string|null that are no longer reported

Root cause

ErrorType extends MixedType, so TypeCombinator::union(int, ErrorType, float) collapses to ErrorType (MixedType absorbs everything). When UnionType::toNumber() maps toNumber() over each member and one returns ErrorType, the entire union's toNumber() becomes ErrorType. In resolveCommonMath(), this triggers return new ErrorType(), which then propagates through the rest of the loop iteration, eventually degrading the variable to mixed.

The fix extracts valid numeric parts from union members in the arithmetic path, only skipping ErrorType from string members. This is safe because PHP's string-to-number conversion always produces a result (int or float), even for non-numeric strings.

Test

Added tests/PHPStan/Analyser/nsrt/bug-11129b.php with two test methods:

  • sayHello(): original issue reproduction using ++$pos with conditional '0' . $pos concatenation in a foreach loop
  • withPlusOne(): same pattern using $pos + 1 instead of ++$pos

Both verify that $pos has type 0|float|(non-falsy-string&uppercase-string) after the loop instead of mixed.

Fixes phpstan/phpstan#11129

…cading to ErrorType

- When a union type like `int|non-falsy-string|float` is used in arithmetic, `toNumber()` returns ErrorType because ErrorType (from the string member) extends MixedType and absorbs all other types in the union
- Added `filterNumberTypeFromUnion()` in `resolveCommonMath()` that extracts valid numeric types from union members, skipping ErrorType only when it comes from string members (since string-to-number conversion always produces a result in PHP)
- This prevents loop variable types from degrading to `mixed` when a variable alternates between int and string (e.g., `$pos = '0' . $pos` in a foreach loop)
- New regression test in tests/PHPStan/Analyser/nsrt/bug-11129b.php
- Removed 3 baseline entries and 1 inline ignore that are no longer needed
Copy link
Copy Markdown
Contributor

@VincentLanglet VincentLanglet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole strategy is wrong and should be reverted.

The issue is related to the for loop cause the same code written this way

$pos = 0;
$pos = '0' . $pos;
$pos = '0' . $pos;
++$pos;

produce no errors.

The first '0' . $positiveInt is understood as numeric-string
But then '0' . $numericString is not a numeric-string anymore. '0' . '-0' is not for instance.
While phpstan has positive-int type ; there is no positive-numeric-string.

Not sure if there is an easy fix

…from cascading to ErrorType"

This reverts commit 5f3ff04.
@phpstan-bot
Copy link
Copy Markdown
Collaborator Author

All good — both background tasks confirmed passing as well. Everything is clean and pushed.

@VincentLanglet VincentLanglet deleted the create-pull-request/patch-ou1v20i branch April 11, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants