Skip to content

Skip string-with-integer-offset decomposition in NonexistentOffsetInArrayDimFetchCheck "might not exist" report#5703

Open
phpstan-bot wants to merge 2 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-97bj22r
Open

Skip string-with-integer-offset decomposition in NonexistentOffsetInArrayDimFetchCheck "might not exist" report#5703
phpstan-bot wants to merge 2 commits into
phpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-97bj22r

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

@phpstan-bot phpstan-bot commented May 18, 2026

…ArrayDimFetchCheck` "might not exist" report

- In the `reportMaybes` decomposition loop of `NonexistentOffsetInArrayDimFetchCheck`,
  when flattened union members are checked individually, skip reporting for string types
  with integer offsets where a specific member returns `no` for `hasOffsetValueType`
- The union-level `hasOffsetValueType` already correctly returns `maybe` for these cases;
  the decomposition adds no new information for strings and produces false positives
  when one union member (e.g. empty string) is shorter than the offset
- Example: `(''|':')[0]` — union-level returns `maybe`, decomposition found `''` returns
  `no` for offset 0 and incorrectly escalated to "might not exist" report
- Definitive `no` results (single string, non-integer offset) are unaffected
- Regression test covers the original issue pattern and string union variants
if (
$innerType->hasOffsetValueType($innerDimType)->no()
) {
if ($innerType->isString()->yes() && $innerDimType->isInteger()->yes()) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this should not be fixed on the rule level. revert this fix.

instead we should keep track of a conditional type for $input based on $inputLen to infer non-empty-string when inputLen > 0 for:

	$inputLen = \strlen($input);
	$hasTrailingColon = $inputLen > 0 && $input[$inputLen-1] === ':';

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done. Here's a summary of the changes:

What was reverted: The rule-level fix in NonexistentOffsetInArrayDimFetchCheck that blanket-skipped string+integer offset checks was removed.

What was added instead: In TypeSpecifier::specifyTypesInCondition(), inside the existing FuncCall handler for named functions (line 583), when strlen() or mb_strlen() is used in a truthy/falsey context, the argument is now narrowed:

  • Truthy context: argument is narrowed to non-empty-string
  • Falsey context: non-empty-string is removed from the argument type (leaving '' for plain string)

How this fixes the bug: When $len = strlen($input) is assigned, the AssignHandler calls specifyTypesInCondition on the RHS in truthy context. The new narrowing produces a SpecifiedTypes with $input => non-empty-string, which gets turned into a ConditionalExpressionHolder: "when $len is truthy (positive), then $input is non-empty-string". Later, when $len > 0 is evaluated, $len is narrowed to positive-int, the conditional expression fires, and $input gets narrowed to non-empty-string.

Test updates:

  • Rule test data simplified to just the original reproducer
  • Added nsrt test verifying indirect narrowing through variable assignment
  • Updated narrow-cast.php expectations to reflect that (bool) strlen($x) now correctly narrows $x

…uthy context

Revert the rule-level fix that skipped string-with-integer-offset checks
in NonexistentOffsetInArrayDimFetchCheck. Instead, teach the TypeSpecifier
to narrow strlen()/mb_strlen() arguments to non-empty-string when the
result is used in a truthy context. This enables the existing
ConditionalExpressionHolder machinery to propagate the narrowing through
variable assignments like `$len = strlen($x); if ($len > 0)`.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
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