Skip to content

Fix #12574: Erroneous Offset might not exist#4965

Closed
phpstan-bot wants to merge 9 commits into2.1.xfrom
create-pull-request/patch-8yeqewa
Closed

Fix #12574: Erroneous Offset might not exist#4965
phpstan-bot wants to merge 9 commits into2.1.xfrom
create-pull-request/patch-8yeqewa

Conversation

@phpstan-bot
Copy link
Collaborator

@phpstan-bot phpstan-bot commented Feb 16, 2026

Summary

When the result of isset() on a property-accessed array element (e.g., $notEmpty = isset($a->worlds[0])) was stored in a variable and later used in a condition, PHPStan failed to narrow the type of the property access. This caused a false positive "Offset might not exist" error. The same code worked correctly when the array was first assigned to a local variable.

Changes

  • Modified processSureTypesForConditionalExpressionsAfterAssign in src/Analyser/NodeScopeResolver.php to also store conditional expressions for PropertyFetch, ArrayDimFetch, and StaticPropertyFetch expressions (previously only Variable expressions were stored)
  • Made the same change to processSureNotTypesForConditionalExpressionsAfterAssign
  • Added regression test tests/PHPStan/Rules/Arrays/data/bug-12574c.php and test method testBug12574c in tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

Root cause

When $notEmpty = isset($a->worlds[0]) is processed, the TypeSpecifier correctly determines that if the result is truthy, $a->worlds has HasOffsetType(0). This information is stored as a "sure type" with the expression being a PropertyFetch node. However, processSureTypesForConditionalExpressionsAfterAssign had an early continue for any expression that was not a Variable, discarding the narrowing information for property fetches, array dim fetches, and static property fetches. When if ($notEmpty) was later evaluated, there was no conditional expression to apply the HasOffsetType(0) narrowing to $a->worlds, resulting in the false positive.

Test

The regression test reproduces the exact scenario from the issue: isset() on $a->worlds[0] stored in a variable, then used in a boolean condition. Both the property-access version (previously failing) and the local-variable version (previously passing) are tested to confirm no errors are reported.

Fixes phpstan/phpstan#12574

Closes phpstan/phpstan#10040
Closes phpstan/phpstan#11102
Closes phpstan/phpstan#11870
Closes phpstan/phpstan#6991
Closes phpstan/phpstan#7716

phpstan-bot and others added 7 commits February 16, 2026 23:43
…operty access

- Extended processSureTypesForConditionalExpressionsAfterAssign and
  processSureNotTypesForConditionalExpressionsAfterAssign to store conditional
  expressions for PropertyFetch, ArrayDimFetch, and StaticPropertyFetch in
  addition to Variable expressions
- Previously, when `$notEmpty = isset($a->worlds[0])` was used in a condition
  like `if ($notEmpty && $a->worlds[0]->x === 1)`, the type narrowing for
  `$a->worlds` (HasOffsetType) was discarded because only Variable expressions
  were stored as conditional expressions
- New regression test in tests/PHPStan/Rules/Arrays/data/bug-12574c.php

Closes phpstan/phpstan#12574
Closes phpstan/phpstan#11870

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes phpstan/phpstan#11102

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes phpstan/phpstan#10040

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes phpstan/phpstan#7716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes phpstan/phpstan#6991

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closes phpstan/phpstan#6120

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@staabm
Copy link
Contributor

staabm commented Feb 18, 2026

@phpstan-bot rebase this branch and fix tests/PHPStan/Analyser/nsrt/bug-10040.php by putting declare(strict_types = 1); after the // lint >= 8.0 comment

@phpstan-bot
Copy link
Collaborator Author

phpstan-bot commented Feb 18, 2026

Claude finished @staabm's task in 1m 8s —— View job


Task completed

  • Rebase branch onto 2.1.x — unable to perform rebase due to tool permission restrictions (git rebase and git config commands are not allowed). A repository maintainer will need to rebase manually.
  • Fix tests/PHPStan/Analyser/nsrt/bug-10040.php — moved declare(strict_types = 1); after // lint >= 8.0 comment ✓
  • Push changes ✓ (commit a538aa4)

The file fix has been pushed. The // lint >= 8.0 comment is now on line 1, followed by <?php declare(strict_types = 1); on line 2.

I was unable to rebase the branch because git rebase and git config commands are not permitted in this environment. A maintainer will need to run git rebase origin/2.1.x manually.

Co-authored-by: Markus Staab <staabm@users.noreply.github.com>
@staabm
Copy link
Contributor

staabm commented Feb 18, 2026

@phpstan-bot revert the last commit

@VincentLanglet I will fix the tests after merge (after your review/approval)

@phpstan-bot
Copy link
Collaborator Author

phpstan-bot commented Feb 18, 2026

Claude finished @staabm's task in 49s —— View job


Task completed

  • Identify the last commit — a538aa422 Fix bug-10040.php: put declare(strict_types) after lint comment
  • Revert it — restored bug-10040.php to its previous state
  • Push changes — pushed commit a56a74a4c

Reverted the last commit (a538aa422). The file tests/PHPStan/Analyser/nsrt/bug-10040.php is back to having <?php declare(strict_types = 1); // lint >= 8.0 on a single line.

This reverts commit a538aa4.

Co-authored-by: Markus Staab <staabm@users.noreply.github.com>
continue;
}
} elseif (
!$expr instanceof PropertyFetch
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like we're missing a lot:

In the end, I'm not sure which are the skipped $expr

@staabm
Copy link
Contributor

staabm commented Feb 18, 2026

cherry picked into #4988

@staabm staabm closed this Feb 18, 2026
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.

3 participants