@@ -126,6 +126,7 @@ public static bool TryGetStateMachineType (MethodDefinition method, [NotNullWhen
126
126
127
127
var callGraph = new CompilerGeneratedCallGraph ( ) ;
128
128
var userDefinedMethods = new HashSet < MethodDefinition > ( ) ;
129
+ var generatedTypeToTypeArgs = new Dictionary < TypeDefinition , TypeArgumentInfo > ( ) ;
129
130
130
131
void ProcessMethod ( MethodDefinition method )
131
132
{
@@ -152,14 +153,15 @@ void ProcessMethod (MethodDefinition method)
152
153
if ( referencedMethod == null )
153
154
continue ;
154
155
156
+ // Find calls to state machine constructors that occur outside the type
155
157
if ( referencedMethod . IsConstructor &&
156
158
referencedMethod . DeclaringType is var generatedType &&
157
159
// Don't consider calls in the same type, like inside a static constructor
158
160
method . DeclaringType != generatedType &&
159
161
CompilerGeneratedNames . IsLambdaDisplayClass ( generatedType . Name ) ) {
160
162
// fill in null for now, attribute providers will be filled in later
161
- if ( ! _generatedTypeToTypeArgumentInfo . TryAdd ( generatedType , new TypeArgumentInfo ( method , null ) ) ) {
162
- var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo [ generatedType ] . CreatingMethod ;
163
+ if ( ! generatedTypeToTypeArgs . TryAdd ( generatedType , new TypeArgumentInfo ( method , null ) ) ) {
164
+ var alreadyAssociatedMethod = generatedTypeToTypeArgs [ generatedType ] . CreatingMethod ;
163
165
_context . LogWarning ( new MessageOrigin ( method ) , DiagnosticId . MethodsAreAssociatedWithUserMethod , method . GetDisplayName ( ) , alreadyAssociatedMethod . GetDisplayName ( ) , generatedType . GetDisplayName ( ) ) ;
164
166
}
165
167
continue ;
@@ -189,7 +191,7 @@ referencedMethod.DeclaringType is var generatedType &&
189
191
// Don't consider field accesses in the same type, like inside a static constructor
190
192
method . DeclaringType != generatedType &&
191
193
CompilerGeneratedNames . IsLambdaDisplayClass ( generatedType . Name ) ) {
192
- if ( ! _generatedTypeToTypeArgumentInfo . TryAdd ( generatedType , new TypeArgumentInfo ( method , null ) ) ) {
194
+ if ( ! generatedTypeToTypeArgs . TryAdd ( generatedType , new TypeArgumentInfo ( method , null ) ) ) {
193
195
// It's expected that there may be multiple methods associated with the same static closure environment.
194
196
// All of these methods will substitute the same type arguments into the closure environment
195
197
// (if it is generic). Don't warn.
@@ -214,7 +216,7 @@ referencedMethod.DeclaringType is var generatedType &&
214
216
}
215
217
// Already warned above if multiple methods map to the same type
216
218
// Fill in null for argument providers now, the real providers will be filled in later
217
- _generatedTypeToTypeArgumentInfo [ stateMachineType ] = new TypeArgumentInfo ( method , null ) ;
219
+ generatedTypeToTypeArgs [ stateMachineType ] = new TypeArgumentInfo ( method , null ) ;
218
220
}
219
221
}
220
222
@@ -280,9 +282,17 @@ referencedMethod.DeclaringType is var generatedType &&
280
282
281
283
// Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute
282
284
// providers
283
- foreach ( var generatedType in _generatedTypeToTypeArgumentInfo . Keys ) {
284
- if ( HasGenericParameters ( generatedType ) )
285
- MapGeneratedTypeTypeParameters ( generatedType ) ;
285
+ foreach ( var generatedType in generatedTypeToTypeArgs . Keys ) {
286
+ if ( HasGenericParameters ( generatedType ) ) {
287
+ MapGeneratedTypeTypeParameters ( generatedType , generatedTypeToTypeArgs , _context ) ;
288
+ // Finally, add resolved type arguments to the cache
289
+ var info = generatedTypeToTypeArgs [ generatedType ] ;
290
+ if ( ! _generatedTypeToTypeArgumentInfo . TryAdd ( generatedType , info ) ) {
291
+ var method = info . CreatingMethod ;
292
+ var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo [ generatedType ] . CreatingMethod ;
293
+ _context . LogWarning ( new MessageOrigin ( method ) , DiagnosticId . MethodsAreAssociatedWithUserMethod , method . GetDisplayName ( ) , alreadyAssociatedMethod . GetDisplayName ( ) , generatedType . GetDisplayName ( ) ) ;
294
+ }
295
+ }
286
296
}
287
297
288
298
_cachedTypeToCompilerGeneratedMembers . Add ( type , compilerGeneratedCallees ) ;
@@ -301,18 +311,42 @@ static bool HasGenericParameters (TypeDefinition typeDef)
301
311
return typeDef . GenericParameters . Count > typeDef . DeclaringType . GenericParameters . Count ;
302
312
}
303
313
304
- void MapGeneratedTypeTypeParameters ( TypeDefinition generatedType )
314
+ /// <summary>
315
+ /// Attempts to reverse the process of the compiler's alpha renaming. So if the original code was
316
+ /// something like this:
317
+ /// <code>
318
+ /// void M<T> () {
319
+ /// Action a = () => { Console.WriteLine (typeof (T)); };
320
+ /// }
321
+ /// </code>
322
+ /// The compiler will generate a nested class like this:
323
+ /// <code>
324
+ /// class <>c__DisplayClass0<T> {
325
+ /// public void <M>b__0 () {
326
+ /// Console.WriteLine (typeof (T));
327
+ /// }
328
+ /// }
329
+ /// </code>
330
+ /// The task of this method is to figure out that the type parameter T in the nested class is the same
331
+ /// as the type parameter T in the parent method M.
332
+ /// <paramref name="generatedTypeToTypeArgs"/> acts as a memoization table to avoid recalculating the
333
+ /// mapping multiple times.
334
+ /// </summary>
335
+ static void MapGeneratedTypeTypeParameters (
336
+ TypeDefinition generatedType ,
337
+ Dictionary < TypeDefinition , TypeArgumentInfo > generatedTypeToTypeArgs ,
338
+ LinkContext context )
305
339
{
306
340
Debug . Assert ( CompilerGeneratedNames . IsGeneratedType ( generatedType . Name ) ) ;
307
341
308
- var typeInfo = _generatedTypeToTypeArgumentInfo [ generatedType ] ;
342
+ var typeInfo = generatedTypeToTypeArgs [ generatedType ] ;
309
343
if ( typeInfo . OriginalAttributes is not null ) {
310
344
return ;
311
345
}
312
346
var method = typeInfo . CreatingMethod ;
313
347
if ( method . Body is { } body ) {
314
348
var typeArgs = new ICustomAttributeProvider [ generatedType . GenericParameters . Count ] ;
315
- var typeRef = ScanForInit ( generatedType , body ) ;
349
+ var typeRef = ScanForInit ( generatedType , body , context ) ;
316
350
if ( typeRef is null ) {
317
351
return ;
318
352
}
@@ -334,9 +368,9 @@ void MapGeneratedTypeTypeParameters (TypeDefinition generatedType)
334
368
var owningRef = ( TypeReference ) owner ;
335
369
if ( ! CompilerGeneratedNames . IsGeneratedType ( owningRef . Name ) ) {
336
370
userAttrs = param ;
337
- } else if ( _context . TryResolve ( ( TypeReference ) param . Owner ) is { } owningType ) {
338
- MapGeneratedTypeTypeParameters ( owningType ) ;
339
- if ( _generatedTypeToTypeArgumentInfo [ owningType ] . OriginalAttributes is { } owningAttrs ) {
371
+ } else if ( context . TryResolve ( ( TypeReference ) param . Owner ) is { } owningType ) {
372
+ MapGeneratedTypeTypeParameters ( owningType , generatedTypeToTypeArgs , context ) ;
373
+ if ( generatedTypeToTypeArgs [ owningType ] . OriginalAttributes is { } owningAttrs ) {
340
374
userAttrs = owningAttrs [ param . Position ] ;
341
375
} else {
342
376
Debug . Assert ( false , "This should be impossible in valid code" ) ;
@@ -348,27 +382,30 @@ void MapGeneratedTypeTypeParameters (TypeDefinition generatedType)
348
382
typeArgs [ i ] = userAttrs ;
349
383
}
350
384
351
- _generatedTypeToTypeArgumentInfo [ generatedType ] = typeInfo with { OriginalAttributes = typeArgs } ;
385
+ generatedTypeToTypeArgs [ generatedType ] = typeInfo with { OriginalAttributes = typeArgs } ;
352
386
}
353
387
}
354
388
355
- GenericInstanceType ? ScanForInit ( TypeDefinition compilerGeneratedType , MethodBody body )
389
+ static GenericInstanceType ? ScanForInit (
390
+ TypeDefinition compilerGeneratedType ,
391
+ MethodBody body ,
392
+ LinkContext context )
356
393
{
357
- foreach ( var instr in _context . GetMethodIL ( body ) . Instructions ) {
394
+ foreach ( var instr in context . GetMethodIL ( body ) . Instructions ) {
358
395
bool handled = false ;
359
396
switch ( instr . OpCode . Code ) {
360
397
case Code . Initobj :
361
398
case Code . Newobj : {
362
399
if ( instr . Operand is MethodReference { DeclaringType : GenericInstanceType typeRef }
363
- && compilerGeneratedType == _context . TryResolve ( typeRef ) ) {
400
+ && compilerGeneratedType == context . TryResolve ( typeRef ) ) {
364
401
return typeRef ;
365
402
}
366
403
handled = true ;
367
404
}
368
405
break ;
369
406
case Code . Stsfld : {
370
407
if ( instr . Operand is FieldReference { DeclaringType : GenericInstanceType typeRef }
371
- && compilerGeneratedType == _context . TryResolve ( typeRef ) ) {
408
+ && compilerGeneratedType == context . TryResolve ( typeRef ) ) {
372
409
return typeRef ;
373
410
}
374
411
handled = true ;
@@ -381,7 +418,7 @@ void MapGeneratedTypeTypeParameters (TypeDefinition generatedType)
381
418
if ( ! handled && instr . OpCode . OperandType is OperandType . InlineMethod ) {
382
419
if ( instr . Operand is GenericInstanceMethod gim ) {
383
420
foreach ( var tr in gim . GenericArguments ) {
384
- if ( tr is GenericInstanceType git && compilerGeneratedType == _context . TryResolve ( git ) ) {
421
+ if ( tr is GenericInstanceType git && compilerGeneratedType == context . TryResolve ( git ) ) {
385
422
return git ;
386
423
}
387
424
}
0 commit comments