Skip to content

Fix: PeelWhereChain rebases onto innermost (narrowest) predicate param#480

Merged
snaumenko-st merged 7 commits into
master-servicetitanfrom
aggregate-fuse-fix
Apr 25, 2026
Merged

Fix: PeelWhereChain rebases onto innermost (narrowest) predicate param#480
snaumenko-st merged 7 commits into
master-servicetitanfrom
aggregate-fuse-fix

Conversation

@snaumenko-st
Copy link
Copy Markdown

@snaumenko-st snaumenko-st commented Apr 24, 2026

When aggregate fusion peels a chain of Where calls with mixed parameter types (e.g. Where<Derived>(p).Where<Base>(q).Sum(...) produced by IQueryable<out T> covariance), rebasing every peeled body onto the outer widest parameter threw ArgumentException on any member that only exists on a narrower type. Rebasing onto the innermost parameter is always safe because every derived type contains all base/interface members.

The non-fusable Sum/Min/Max/Avg peel path rebuilt the Where call with the predicate unconditionally wrapped in Expression.Quote. That is correct for Queryable.Where (parameter type is Expression<Func<>>) but throws
ArgumentException for Enumerable.Where (parameter type is Func<>), which is what g.Where(p) on an IGrouping resolves to. Quote only when the captured innermost Where method expects an Expression-derived parameter.

Made-with: Cursor

snaumenko-st and others added 4 commits April 24, 2026 21:41
When aggregate fusion peels a chain of Where calls with mixed parameter
types (e.g. Where<Derived>(p).Where<Base>(q).Sum(...) produced by
IQueryable<out T> covariance), rebasing every peeled body onto the outer
widest parameter threw ArgumentException on any member that only exists
on a narrower type. Rebasing onto the innermost parameter is always safe
because every derived type contains all base/interface members.

Made-with: Cursor
…ate peel

The non-fusable Sum/Min/Max/Avg peel path rebuilt the Where call with the
predicate unconditionally wrapped in Expression.Quote. That is correct for
Queryable.Where (parameter type is Expression<Func<>>) but throws
ArgumentException for Enumerable.Where (parameter type is Func<>), which
is what g.Where(p) on an IGrouping resolves to. Quote only when the
captured innermost Where method expects an Expression-derived parameter.

Made-with: Cursor
Seed the AndAlso accumulator from peeled[^1].Body (already typed against
the rebase parameter) and iterate only the outer predicates that need
rebasing. Drops the per-iteration null check and reduces FastExpression.Lambda
allocations from one-per-predicate to one total.

Made-with: Cursor
… place

source is already a ref Expression; the local copy and final write-back
were redundant. The bail-out branches naturally leave source at the
unconsumed remainder.

Made-with: Cursor
@snaumenko-st snaumenko-st merged commit 579f681 into master-servicetitan Apr 25, 2026
4 checks passed
@snaumenko-st snaumenko-st deleted the aggregate-fuse-fix branch April 25, 2026 07:48
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