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
An instance field referenced inside an Update().Set(...)computed expression, alongside a captured
local, produces an interceptor that fails at runtime. Two defects stack on the same path:
The [UnsafeAccessorType("…")] string is built with SymbolDisplayFormat.FullyQualifiedFormat, which
emits a global:: prefix. That attribute takes a metadata type name, where global:: is invalid,
so the accessor throws at first use:
System.TypeLoadException : Could not resolve type 'global::MyApp.MyClass' in assembly 'MyApp'
Even with the name corrected, the field would still be read off the wrong object. When a lambda
captures an instance field and a local, the compiler interposes a display class and the field lives
on the instance behind its <>4__this back-reference. The parameter path handles this (added in Chains inside doubly-nested lambdas emit interceptors that fail to compile (CS0103 on captured locals) #333);
the SetActionAllCapturedIdentifiers path does not, and would throw InvalidCastException.
Reproduction:
privatereadonlystring_prefix="Pre";varsuffix="X";awaitdb.Users().Update().Set(u =>u.UserName=_prefix+suffix)// field + local in a computed Set expression.Where(u =>u.UserId==1).ExecuteNonQueryAsync();
Location
src/Quarry.Generator/Parsing/UsageSiteDiscovery.cs — the containingClass value is produced with SymbolDisplayFormat.FullyQualifiedFormat (search field.ContainingType?.ToDisplayString).
src/Quarry.Generator/CodeGen/CarrierAnalyzer.cs — the second extractor pass over SetActionAllCapturedIdentifiers, which sets effectiveCaptureKind = FieldCapture and emits no <>4__this hop.
Diagnostics
System.TypeLoadException : Could not resolve type 'global::…', thrown from the generated __ExtractVar__prefix_0 accessor. Inspect the emitted file under obj/GeneratedFiles/Quarry.Generator/Quarry.Generators.QuarryGenerator/ — the bad attribute is visible
directly:
[UnsafeAccessor(UnsafeAccessorKind.Field,Name="_prefix")]internalexternstaticrefstring__ExtractVar__prefix_0([UnsafeAccessorType("global::MyApp.MyClass")]objecttarget);// ← global:: is invalid here
Note the sibling accessor for the captured local on the same carrier is correct, which makes the
difference easy to see.
[UnsafeAccessorType] takes a metadata type name: nested types use +, and there is no global::
qualifier. The working Where path derives its string from SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces instead.
The <>4__this hop is expressible with a plain [UnsafeAccessor] because that field's type is the
user's own class — no [return: UnsafeAccessorType] is needed, so dotnet/runtime#119664 does not block it. See the
"Display Class Prediction" section of src/Quarry.Generator/llm.md.
Add a failing execution test for the shape above (it must execute — a wrong accessor still compiles).
Build the SetActionAllCapturedIdentifiers type string the same way the parameter path does, without
the global:: prefix.
Reuse the <>4__this indirection for instance fields on that path, including the IsNameableFromGeneratedCode fallback to a QRY032 disqualification.
Ideally collapse the two extractor-building passes in CarrierAnalyzer so this class of divergence
cannot recur — they now differ in three ways for no intended reason.
Description
An instance field referenced inside an
Update().Set(...)computed expression, alongside a capturedlocal, produces an interceptor that fails at runtime. Two defects stack on the same path:
The
[UnsafeAccessorType("…")]string is built withSymbolDisplayFormat.FullyQualifiedFormat, whichemits a
global::prefix. That attribute takes a metadata type name, whereglobal::is invalid,so the accessor throws at first use:
Even with the name corrected, the field would still be read off the wrong object. When a lambda
captures an instance field and a local, the compiler interposes a display class and the field lives
on the instance behind its
<>4__thisback-reference. The parameter path handles this (added in Chains inside doubly-nested lambdas emit interceptors that fail to compile (CS0103 on captured locals) #333);the
SetActionAllCapturedIdentifierspath does not, and would throwInvalidCastException.Reproduction:
Location
src/Quarry.Generator/Parsing/UsageSiteDiscovery.cs— thecontainingClassvalue is produced withSymbolDisplayFormat.FullyQualifiedFormat(searchfield.ContainingType?.ToDisplayString).src/Quarry.Generator/CodeGen/CarrierAnalyzer.cs— the second extractor pass overSetActionAllCapturedIdentifiers, which setseffectiveCaptureKind = FieldCaptureand emits no<>4__thishop.Diagnostics
System.TypeLoadException : Could not resolve type 'global::…', thrown from the generated__ExtractVar__prefix_0accessor. Inspect the emitted file underobj/GeneratedFiles/Quarry.Generator/Quarry.Generators.QuarryGenerator/— the bad attribute is visibledirectly:
Note the sibling accessor for the captured local on the same carrier is correct, which makes the
difference easy to see.
What Has Been Tried
stashed (
git stash push src/Quarry.Generator/), producing the identicalTypeLoadException.Setcomputed-expression path specifically. The equivalent capture in aWhereclause works, because that goes through the parameter path which Chains inside doubly-nested lambdas emit interceptors that fail to compile (CS0103 on captured locals) #333 taught to emit a<>4__thishop and which builds its type string withoutglobal::.Gathered Information
[UnsafeAccessorType]takes a metadata type name: nested types use+, and there is noglobal::qualifier. The working
Wherepath derives its string fromSymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespacesinstead.<>4__thishop is expressible with a plain[UnsafeAccessor]because that field's type is theuser's own class — no
[return: UnsafeAccessorType]is needed, sodotnet/runtime#119664 does not block it. See the
"Display Class Prediction" section of
src/Quarry.Generator/llm.md.containing type is generic or not visible to generated code, or the emitted accessor is a CS0305 /
CS0122 build break.
DisplayClassEnricher.IsNameableFromGeneratedCodealready implements that test.Suggested Approach
SetActionAllCapturedIdentifierstype string the same way the parameter path does, withoutthe
global::prefix.<>4__thisindirection for instance fields on that path, including theIsNameableFromGeneratedCodefallback to a QRY032 disqualification.CarrierAnalyzerso this class of divergencecannot recur — they now differ in three ways for no intended reason.