@@ -14,10 +14,11 @@ private async Task AddLocalVariables(ModuleInfo module, CorDebugFunction corDebu
1414 if ( classContainingHoistedLocalsValue is not null )
1515 {
1616 // If we have a classContainingHoistedLocalsValue, it means captured variables from the outer scope are stored
17- // as fields on the compiler-generated closure class - read those first.
17+ // as fields on the compiler-generated closure class - read those first, walking the full closure chain
18+ // so that variables captured from enclosing lambdas are also included.
1819 // We do NOT return here: non-captured locals declared inside the lambda body are still plain IL locals
1920 // on the lambda method frame and must also be read below.
20- await AddMembers ( classContainingHoistedLocalsValue , classContainingHoistedLocalsValue . ExactType , threadId , stackDepth , result ) ;
21+ await AddClosureChainMembers ( classContainingHoistedLocalsValue , threadId , stackDepth , result ) ;
2122 }
2223 var corDebugIlFrame = GetFrameForThreadIdAndStackDepth ( threadId , stackDepth ) ;
2324 if ( corDebugIlFrame . LocalVariables . Length is 0 ) return ;
@@ -39,6 +40,29 @@ private async Task AddLocalVariables(ModuleInfo module, CorDebugFunction corDebu
3940 }
4041 }
4142
43+ /// Walks the compiler-generated closure chain starting at <paramref name="closureValue"/>,
44+ /// calling AddMembers on each closure class. Parent closures are linked via a field of
45+ /// kind <see cref="GeneratedNameKind.DisplayClassLocalOrField"/> (e.g. "<>8__1").
46+ private async Task AddClosureChainMembers ( CorDebugValue closureValue , ThreadId threadId , FrameStackDepth stackDepth , List < VariableInfo > result )
47+ {
48+ await AddMembers ( closureValue , closureValue . ExactType , threadId , stackDepth , result ) ;
49+
50+ // Follow the DisplayClassLocalOrField link to the parent closure, if any
51+ var objectValue = closureValue . UnwrapDebugValueToObject ( ) ;
52+ var metadataImport = objectValue . Class . Module . GetMetaDataInterface ( ) . MetaDataImport ;
53+ var fields = metadataImport . EnumFields ( objectValue . Class . Token ) ;
54+ foreach ( var field in fields )
55+ {
56+ var fieldProps = metadataImport . GetFieldProps ( field ) ;
57+ if ( GeneratedNameParser . GetKind ( fieldProps . szField ) is GeneratedNameKind . DisplayClassLocalOrField )
58+ {
59+ var parentClosureValue = objectValue . GetFieldValue ( objectValue . Class . Raw , field ) ;
60+ await AddClosureChainMembers ( parentClosureValue , threadId , stackDepth , result ) ;
61+ break ; // only one parent link per closure class
62+ }
63+ }
64+ }
65+
4266 /// Returns classContainingHoistedLocalsValue if applicable
4367 private async Task < CorDebugValue ? > AddArguments ( ModuleInfo module , CorDebugFunction corDebugFunction , List < VariableInfo > result , ThreadId threadId , FrameStackDepth stackDepth )
4468 {
0 commit comments