Repository navigation
Reuse a stored narrowing result only when the asking scope matches - #6702
Open
pindab0ter wants to merge 2 commits into
Open
pindab0ter wants to merge 2 commits into
pindab0ter wants to merge 2 commits into
Conversation
array_filter() and array_find() narrow by their callback body with the parameter re-bound to the element type. obtainResultForNode() returned the body's stored result, whose narrowing was computed for the declared parameter type, so with `fn (array $frame) => isset($frame['file'])` mixed elements were narrowed to (array|ArrayAccess)&hasOffsetValue. getType() already re-prices such an ask. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0138Sy8qW1oxcf7yyQ2sWCub
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A 2.3.0 regression (2.2.16 is fine) when an
array_filter()callback with a typed parameter narrows byisset():Passing an element on to an
array{file: string, ...}parameter then reportsargument.type. Without thearrayparameter type, both versions givelist<mixed~null>.!empty(),array_find()andARRAY_FILTER_USE_BOTHregressed the same way.Cause
ArrayFilterFunctionReturnTypeHelper::processKeyAndItemType()binds the callback parameter to the element type (mixed) and asks for the body's type and narrowing on that scope. The body itself was walked with$frameasarray, so both are counterfactual asks.resolveTypeOfNewWorldHandlerNode(), which checksaskScopeVariableStateMatches()and re-prices the node on the asking scope when a variable it reads differs.filterByTruthyValue()→specifyTypesOfNewWorldHandlerNode()→obtainResultForNode()) returned the stored result without that check. Its narrowing was computed where$frameisarray, soisset()emittedHasOffsetType('file'), which it skips for amixedcontainer. Applied tomixed, that becomes(array|ArrayAccess)&hasOffsetValue('file', ...).Fix
obtainResultForNode()returns the stored result only whenaskScopeVariableStateMatches()holds, and otherwise processes the node on demand on the asking scope, as the type path does. The turbo mirror gets the same check.The guard sits at the reuse point rather than in the
isset()narrowing because every narrowing callback runs at its node's own evaluation point (seeExpressionResult::getSpecifiedTypes()), so any of them can read a type that a counterfactual ask re-binds. Guarding the reuse point covers all handlers, as it already does forgetType().It uses the strict, engine-side comparison. The rule-facing variant (
$ruleFacingAsk) accepts an asking type that is wider than the walk-position type, which is exactlymixedagainstarray, so it would keep the stale answer.Outside
array_filter()/array_find(), the test suites reach the new branch only inbug-14604((... ?? []) ?: throw, re-asked byAssignHandleron the post-condition scope) and inNodeCallbackScopeFilterByValueRule's chainedfilterByTruthyValue(). Both now narrow on the asking scope, as 2.2 did, and their expectations are unchanged. The check returns early when the asking scope is the stored result's ownbeforeScope.🤖 Generated with Claude Code
https://claude.ai/code/session_0138Sy8qW1oxcf7yyQ2sWCub