Skip to content

Fix #4332: string[].Contains parse failure under C# 14 + nullable + method init#4333

Merged
jeremydmiller merged 1 commit intomasterfrom
fix-issue-4332-string-array-contains
May 6, 2026
Merged

Fix #4332: string[].Contains parse failure under C# 14 + nullable + method init#4333
jeremydmiller merged 1 commit intomasterfrom
fix-issue-4332-string-array-contains

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Summary

Fixes #4332.

C# 14 with <Nullable>enable</Nullable> wraps captured-closure string[] locals that were initialised from a method-typed return (var v = MakeStrings();) with an extra Convert() before the implicit string[] -> ReadOnlySpan<string> conversion:

s => op_Implicit(Convert(closureField, String[])).Contains(s.Name)

MemoryExtensionsContains.UnwrapConversions only peeled the outer op_Implicit MethodCallExpression, leaving the inner Convert UnaryExpression in place. The receiver then failed TryToParseConstant, parsing fell through to ValueCollectionMember.ParseWhereForContains, and CompileFast'ing the whole Where lambda threw InvalidOperationException: variable 's' ... referenced from scope '', but it is not defined at parse time.

The fix strips Convert/ConvertChecked UnaryExpressions (and op_Explicit) in a loop alongside op_Implicit, so any stacked wrapper combination canonicalises to the captured field and reduces cleanly to a constant. The query then takes the fast IsOneOfFilter path that the literal-init / .ToList() workaround was already hitting.

Test plan

  • Added Bug_4332_string_array_contains_under_nullable_method_init with two facts:
    • can_query_when_string_array_local_is_method_initialised — natural-pattern repro using #nullable enable + var values = MakeNames(...); (triggers under net10.0 / C# 14)
    • can_query_when_collection_expression_has_convert_wrapper — builds the failing Convert(closureField, string[]) expression by hand so the bug reproduces deterministically on every TFM
  • Both new tests fail on master with the exact InvalidOperationException from the issue and pass with the fix applied.
  • Full LinqTests suite green on net10.0: 1260 passed / 1 skipped (pre-existing) / 0 failed.

🤖 Generated with Claude Code

C# 14 with <Nullable>enable</Nullable> wraps captured-closure string[]
locals that were initialised from a method-typed return with an extra
Convert() before the implicit string[] -> ReadOnlySpan<string>:

    s => op_Implicit(Convert(closureField, String[])).Contains(s.Name)

UnwrapConversions only peeled op_Implicit, so the receiver failed to
reduce to a constant, parsing fell through to ParseWhereForContains,
and CompileFast'ing the whole Where lambda threw
InvalidOperationException about the lambda parameter not being in
scope. Strip Convert/ConvertChecked unaries (and op_Explicit) in a
loop alongside op_Implicit so any stacked wrapper combination
canonicalises to the captured field.

Closes #4332.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeremydmiller jeremydmiller merged commit f175367 into master May 6, 2026
6 checks passed
@jeremydmiller jeremydmiller deleted the fix-issue-4332-string-array-contains branch May 6, 2026 17:58
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.

8.34.2: string[].Contains in Where predicate throws InvalidOperationException under C# 14 + <Nullable>enable</Nullable> when local is method-initialised

1 participant