Skip to content

Fix phpstan/phpstan#14455: missing has-offset via conditional type#5448

Open
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ol85e86
Open

Fix phpstan/phpstan#14455: missing has-offset via conditional type#5448
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ol85e86

Conversation

@phpstan-bot
Copy link
Copy Markdown
Collaborator

Summary

When an early return guard combines empty($arr['key']) with a variable comparison via &&, PHPStan failed to narrow the array type inside a subsequent branch that checked the same variable. For example:

if (empty($aggregation['field']) && $type === 'filter') { return; }
if ($type === 'filter') {
    // $aggregation should be narrowed to hasOffset('field') here
}

PHPStan reported array<string, mixed> instead of non-empty-array<string, mixed>&hasOffset('field').

Changes

  • Added processBooleanNotSureSureConditionalTypes() method in src/Analyser/TypeSpecifier.php — a cross-product of the existing processBooleanNotSureConditionalTypes (conditions) and processBooleanSureConditionalTypes (results) methods
  • Wired the new method into both the BooleanAnd (falsey context) and BooleanOr (truthy context) conditional expression holder creation

Root cause

The TypeSpecifier creates conditional expression holders to express relationships like "when variable X has type T, then variable Y has type U". These holders are checked when entering branches.

For empty($arr['field']) && $type === 'filter' in the falsey context (after early return):

  • The empty() side produces sure types for $arr (intersect with HasOffsetType('field'))
  • The $type === 'filter' side produces sure-NOT-types for $type (remove 'filter')

The existing methods only created holders from matching kinds: sure-type conditions with sure-type results (processBooleanSureConditionalTypes), or sure-NOT-type conditions with sure-NOT-type results (processBooleanNotSureConditionalTypes). The cross-product case — sure-NOT-type conditions with sure-type results — was missing. This meant no holder was created to express "when $type is 'filter', then $arr has hasOffset('field')".

Test

Added tests/PHPStan/Analyser/nsrt/bug-14455.php — verifies that after if (empty($aggregation['field']) && $type === 'filter') { return; }, checking if ($type === 'filter') correctly narrows $aggregation to non-empty-array<string, mixed>&hasOffset('field').

Fixes phpstan/phpstan#14455

- Added processBooleanNotSureSureConditionalTypes method to TypeSpecifier
  that creates conditional expression holders with sure-NOT-type conditions
  and sure-type results (cross-product of existing methods)
- Called the new method in both BooleanAnd (falsey) and BooleanOr (truthy)
  conditional expression holder creation
- New regression test in tests/PHPStan/Analyser/nsrt/bug-14455.php
- Root cause: empty($arr['key']) && $type === 'filter' in an early return
  produced sure-NOT-types for $type and sure-types for $arr, but no existing
  method combined these to create the conditional holder needed for narrowing
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.

1 participant