You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A Quarry chain written inside a lambda that is itself nested in another lambda makes the generator
emit an interceptor that references the enclosing method's locals directly. Those locals live in a
compiler-generated display class the interceptor cannot see, so the generated file fails to compile:
CS0103: The name 'name' does not exist in the current context
in *.Interceptors.*.g.cs. It is a build break with no Quarry diagnostic — the first sign of trouble
is a compiler error inside generated code.
Found while writing the concurrency suite for #314. The natural way to express a parallel worker is:
vartasks=harnesses.Select((h,i)=>Task.Run(async()=>{varname=$"Worker{i}";// local in the inner lambda's display classawaith.Lite.Users().Update().Set(u =>u.UserName=name)// captured by the chain.Where(u =>u.UserId==1).ExecuteNonQueryAsync();}));
The emitted interceptor for that chain refers to name as though it were in scope at the
interception site. It is not — it is a field on the display class the two nested lambdas share.
Location
Generator: display-class resolution for captured variables — src/Quarry.Generator/Parsing/DisplayClassEnricher.cs and src/Quarry.Generator/Parsing/DisplayClassNameResolver.cs.
Workaround in the test suite: src/Quarry.Tests/Integration/ConcurrencyTests.cs. Every worker body
is a named private static async Task<T> Run…WorkerAsync(...) method rather than an inline lambda,
so the chain's captures are ordinary method locals. The fixture <remarks> records why.
Diagnostics
CS0103 in the generated interceptor file, reported for all four fixture contexts
(TestDbContext, PgDb, MyDb, SsDb) when the shape is present. EmitCompilerGeneratedFiles
is already enabled on Quarry.Tests.csproj, so the offending generated source is inspectable under obj/GeneratedFiles/Quarry.Generator/Quarry.Generators.QuarryGenerator/.
What Has Been Tried
Confirmed the nesting is what matters, not the parallelism. The same chain hoisted into a named
method compiles and runs correctly; that is the workaround now in ConcurrencyTests.
Not yet isolated to a minimal repro. The variables worth bisecting, none of which have been
tested in isolation:
one lambda vs. two (does a single non-nested lambda already fail?);
async lambda vs. plain lambda;
a captured loop variable (i) vs. a plain local declared inside the inner lambda;
Task.Run(...) specifically vs. any nested lambda;
whether it reproduces without Select((h, i) => ...) supplying an index.
This is the main thing the issue needs before a fix: a synthetic case in src/Quarry.Tests/Generation/ that fails, so the fix has a test.
Gathered Information
The generator predicts compiler-generated closure class names to emit [UnsafeAccessor] methods
for captured-variable extraction without reflection — see the "Display Class Prediction" section of src/Quarry.Generator/llm.md. Nested lambdas produce nested/chained display classes, and the
prediction is the natural suspect.
The chain analyzer already disqualifies some closure shapes outright (Chain variable captured in lambda → OptimizationTier.RuntimeBuild + QRY032). This shape is not disqualified — it is
accepted and then emitted wrongly, which is why it surfaces as CS0103 rather than a Quarry
diagnostic.
Suggested Approach
Two directions, in order of preference:
Resolve the capture correctly. Teach display-class resolution to walk the nested-lambda chain
so the interceptor reads the local through the display-class instance it actually lives on, the
same way a single-level capture already works.
Fail loudly if (1) is out of scope for now. Detect the nested-lambda-with-captures shape in ChainAnalyzer.CheckDisqualifiers and disqualify it to RuntimeBuild with a QRY diagnostic
naming the shape and the "hoist the chain into a named method" workaround. A build error inside
generated code is the worst possible way for a user to meet this limitation; a QRY error at the
call site is the least surprising.
Either way, add the minimal repro from "What Has Been Tried" to src/Quarry.Tests/Generation/ so the behaviour is pinned.
Description
A Quarry chain written inside a lambda that is itself nested in another lambda makes the generator
emit an interceptor that references the enclosing method's locals directly. Those locals live in a
compiler-generated display class the interceptor cannot see, so the generated file fails to compile:
in
*.Interceptors.*.g.cs. It is a build break with no Quarry diagnostic — the first sign of troubleis a compiler error inside generated code.
Found while writing the concurrency suite for #314. The natural way to express a parallel worker is:
The emitted interceptor for that chain refers to
nameas though it were in scope at theinterception site. It is not — it is a field on the display class the two nested lambdas share.
Location
Generator: display-class resolution for captured variables —
src/Quarry.Generator/Parsing/DisplayClassEnricher.csandsrc/Quarry.Generator/Parsing/DisplayClassNameResolver.cs.Workaround in the test suite:
src/Quarry.Tests/Integration/ConcurrencyTests.cs. Every worker bodyis a named
private static async Task<T> Run…WorkerAsync(...)method rather than an inline lambda,so the chain's captures are ordinary method locals. The fixture
<remarks>records why.Diagnostics
CS0103in the generated interceptor file, reported for all four fixture contexts(
TestDbContext,PgDb,MyDb,SsDb) when the shape is present.EmitCompilerGeneratedFilesis already enabled on
Quarry.Tests.csproj, so the offending generated source is inspectable underobj/GeneratedFiles/Quarry.Generator/Quarry.Generators.QuarryGenerator/.What Has Been Tried
method compiles and runs correctly; that is the workaround now in
ConcurrencyTests.tested in isolation:
asynclambda vs. plain lambda;i) vs. a plain local declared inside the inner lambda;Task.Run(...)specifically vs. any nested lambda;Select((h, i) => ...)supplying an index.This is the main thing the issue needs before a fix: a synthetic case in
src/Quarry.Tests/Generation/that fails, so the fix has a test.Gathered Information
[UnsafeAccessor]methodsfor captured-variable extraction without reflection — see the "Display Class Prediction" section of
src/Quarry.Generator/llm.md. Nested lambdas produce nested/chained display classes, and theprediction is the natural suspect.
shifts and generic containing types. This is a different trigger (lambda nesting depth) and fails
at compile time in the generated file rather than at runtime with a
TypeLoadException, so it isfiled separately rather than folded in.
Chain variable captured in lambda→OptimizationTier.RuntimeBuild+ QRY032). This shape is not disqualified — it isaccepted and then emitted wrongly, which is why it surfaces as CS0103 rather than a Quarry
diagnostic.
Suggested Approach
Two directions, in order of preference:
so the interceptor reads the local through the display-class instance it actually lives on, the
same way a single-level capture already works.
ChainAnalyzer.CheckDisqualifiersand disqualify it toRuntimeBuildwith a QRY diagnosticnaming the shape and the "hoist the chain into a named method" workaround. A build error inside
generated code is the worst possible way for a user to meet this limitation; a QRY error at the
call site is the least surprising.
Either way, add the minimal repro from "What Has Been Tried" to
src/Quarry.Tests/Generation/so the behaviour is pinned.