Skip to content

[8.0 backport] Fix #4599: EnumerableContains.Parse crash on programmatic receivers with custom ToString()#4601

Merged
jeremydmiller merged 1 commit into
8.0from
backport/4599-enumerablecontains-receiver-8.0
Jun 2, 2026
Merged

[8.0 backport] Fix #4599: EnumerableContains.Parse crash on programmatic receivers with custom ToString()#4601
jeremydmiller merged 1 commit into
8.0from
backport/4599-enumerablecontains-receiver-8.0

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Backport of #4600 to the `8.0` branch. The reporter is on 8.37.1 and the same bug exists there with the same code.

Cherry-pick of commit `a5848d272` (the master fix) — applied cleanly with the auto-merge.

Summary

`EnumerableContains.Parse` and `HashSetEnumerableContains.Parse` crashed for `Contains()` receivers built as `Property(Constant(), "")` when the wrapper type overrides `ToString()`. Triggered by HotChocolate's `[UseFiltering]` `in` operator, which builds receivers that way via `FilterExpressionBuilder.CreateAndConvertParameter`. The crash: `FastExpressionCompiler` throwing `variable 'x' of type '' referenced from scope '', but it is not defined`.

Fix:

  • `IsCompilableExpression`: replace the `ToString().StartsWith("value(")` heuristic with a structural check (walk the member chain to its root; accept if it bottoms out at `ConstantExpression`).
  • `ReduceToConstant`: add a free-parameter guard that throws a clear `BadLinqExpressionException` instead of letting FEC surface the confusing scope-binding error.

See #4600 for the full discussion.

Validation on the `8.0` branch

  • Cherry-pick applied cleanly; compiles against the 8.x Weasel.
  • New regression tests (`Bug_4599_enumerable_contains_programmatic_receiver`, Enumerable + HashSet shapes) pass on net8.0 / net9.0 / net10.0 against Postgres.

🤖 Generated with Claude Code

…heck

EnumerableContains.Parse (and the HashSet sibling) crashed for receivers built
as Property(Constant(<wrapper>), "<member>") when the wrapper type overrides
ToString() -- e.g. HotChocolate's ExpressionParameter<T> driving [UseFiltering]
`in`. ConstantExpression.ToString() only wraps in "value(<TypeName>)" when the
value uses the default Object.ToString(); with a custom override, it prints the
custom string directly. The IsCompilableExpression heuristic
(node.Expression.ToString().StartsWith("value(")) then returned false, the
receiver was wrongly rejected by TryToParseConstant, and the parser fell into
ParseWhereForContains -> ReduceToConstant on the *value-side* expression. That
expression still referenced the outer Where lambda parameter, so FEC compiled
a parameter-less lambda with a free parameter inside and crashed with
"variable 'x' of type '<Doc>' referenced from scope '', but it is not defined".

- IsCompilableExpression: walk the member chain to its root and accept if the
  root is a ConstantExpression (or null, for static members). Structural; no
  string heuristic. Accepts the previously-rejected programmatic-receiver shape.
- ReduceToConstant: add a free-parameter guard (visitor that ignores parameters
  bound by lambdas *within* the expression). If a free parameter is found,
  throw a clear BadLinqExpressionException naming it rather than letting FEC
  surface the confusing scope-binding message. Safety net for any other call
  site that might mistakenly feed a parameter-bearing subtree.

Tests cover both Enumerable.Contains and HashSet<T>.Contains receiver shapes
against Postgres; the previously-failing repros now pass. Full
LinqTests Contains/Where/GroupBy/Any/Modulo sweep (437 tests) still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller force-pushed the backport/4599-enumerablecontains-receiver-8.0 branch from 2181cc0 to 1cc2cf6 Compare June 2, 2026 17:08
@jeremydmiller jeremydmiller merged commit 4fb4884 into 8.0 Jun 2, 2026
@jeremydmiller jeremydmiller deleted the backport/4599-enumerablecontains-receiver-8.0 branch June 2, 2026 17:27
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