Skip to content

Fix phpstan/phpstan#13563: Closure / Arrow functions type difference when an array is reset#5186

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

Fix phpstan/phpstan#13563: Closure / Arrow functions type difference when an array is reset#5186
phpstan-bot wants to merge 1 commit intophpstan:2.1.xfrom
phpstan-bot:create-pull-request/patch-ox9jgmu

Conversation

@phpstan-bot
Copy link
Collaborator

Summary

When a property was assigned a narrower type (e.g. $this->dates = [] narrowing array<int, DateTime> to array{}), arrow functions incorrectly inherited this narrowed type from the parent scope. Closures correctly resolved such properties to their declared type, since closures build a fresh scope. This caused false positives like "Offset int on array{} on left side of ?? does not exist" when using $this->dates[$id] ?? null inside an arrow function.

Changes

  • src/Analyser/MutatingScope.php: In enterArrowFunctionWithoutReflection(), filter out non-readonly PropertyFetch expression type narrowings from both expressionTypes and nativeExpressionTypes before creating the arrow function scope. This aligns arrow function behavior with closures, which don't carry property narrowings from the parent scope.
  • src/Type/Generic/TemplateTypeTrait.php: Capture $this->default in a local variable before passing it to an arrow function. This avoids relying on property narrowing leaking through the arrow function scope boundary, which the fix above correctly prevents.

Root cause

Arrow functions inherit the full parent scope (via $arrowFunctionScope = $this), including all expression type holders. This means property narrowings from assignments like $this->dates = [] leaked into the arrow function scope, causing the arrow function to see the narrowed type array{} instead of the declared type array<int, DateTime>.

Closures don't have this problem because enterAnonymousFunctionWithoutReflection() builds a fresh scope with only parameters, use-variables, and $this (plus readonly property fetches). Property types are then resolved from their declarations when accessed inside the closure body.

The fix filters out non-readonly PropertyFetch narrowings when entering arrow function scope, matching the closure behavior.

Test

Added tests/PHPStan/Analyser/nsrt/bug-13563.php — a type inference test that verifies both arrow functions and closures resolve $this->dates to the declared type array<int, DateTime> after $this->dates = [], and that properties not cleared remain at their declared type in both contexts.

Fixes phpstan/phpstan#13563

…cope

- Arrow functions inherited all expression types from the parent scope, including property narrowings from assignments (e.g. $this->prop = [])
- Closures correctly reset property types by building a fresh scope, but arrow functions did not
- Filter out non-readonly PropertyFetch narrowings in enterArrowFunctionWithoutReflection, matching closure behavior
- Fix TemplateTypeTrait to capture $this->default in a local variable before passing to arrow function
- New regression test in tests/PHPStan/Analyser/nsrt/bug-13563.php
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