44using System . IO ;
55using System . Linq ;
66using Microsoft . CodeAnalysis ;
7+ using Semmle . Util ;
78using Semmle . Extraction . CSharp . Entities ;
89
910namespace Semmle . Extraction . CSharp
@@ -164,6 +165,7 @@ public static void BuildTypeId(this ITypeSymbol type, Context cx, EscapingTextWr
164165 case TypeKind . Enum :
165166 case TypeKind . Delegate :
166167 case TypeKind . Error :
168+ case TypeKind . Extension :
167169 var named = ( INamedTypeSymbol ) type ;
168170 named . BuildNamedTypeId ( cx , trapFile , symbolBeingDefined , constructUnderlyingTupleType ) ;
169171 return ;
@@ -275,6 +277,20 @@ private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol f
275277 public static IEnumerable < IFieldSymbol ? > GetTupleElementsMaybeNull ( this INamedTypeSymbol type ) =>
276278 type . TupleElements ;
277279
280+ private static void BuildExtensionTypeId ( this INamedTypeSymbol named , Context cx , EscapingTextWriter trapFile )
281+ {
282+ trapFile . Write ( "extension(" ) ;
283+ if ( named . ExtensionMarkerName is not null )
284+ {
285+ trapFile . Write ( named . ExtensionMarkerName ) ;
286+ }
287+ else
288+ {
289+ trapFile . Write ( "unknown" ) ;
290+ }
291+ trapFile . Write ( ")" ) ;
292+ }
293+
278294 private static void BuildQualifierAndName ( INamedTypeSymbol named , Context cx , EscapingTextWriter trapFile , ISymbol symbolBeingDefined )
279295 {
280296 if ( named . ContainingType is not null )
@@ -289,8 +305,18 @@ private static void BuildQualifierAndName(INamedTypeSymbol named, Context cx, Es
289305 named . ContainingNamespace . BuildNamespace ( cx , trapFile ) ;
290306 }
291307
292- var name = named . IsFileLocal ? named . MetadataName : named . Name ;
293- trapFile . Write ( name ) ;
308+ if ( named . IsFileLocal )
309+ {
310+ trapFile . Write ( named . MetadataName ) ;
311+ }
312+ else if ( named . IsExtension )
313+ {
314+ named . BuildExtensionTypeId ( cx , trapFile ) ;
315+ }
316+ else
317+ {
318+ trapFile . Write ( named . Name ) ;
319+ }
294320 }
295321
296322 private static void BuildTupleId ( INamedTypeSymbol named , Context cx , EscapingTextWriter trapFile , ISymbol symbolBeingDefined )
@@ -391,6 +417,7 @@ public static void BuildDisplayName(this ITypeSymbol type, Context cx, TextWrite
391417 case TypeKind . Enum :
392418 case TypeKind . Delegate :
393419 case TypeKind . Error :
420+ case TypeKind . Extension :
394421 var named = ( INamedTypeSymbol ) type ;
395422 named . BuildNamedTypeDisplayName ( cx , trapFile , constructUnderlyingTupleType ) ;
396423 return ;
@@ -465,6 +492,20 @@ public static void BuildFunctionPointerSignature(IFunctionPointerTypeSymbol funp
465492 private static void BuildFunctionPointerTypeDisplayName ( this IFunctionPointerTypeSymbol funptr , Context cx , TextWriter trapFile ) =>
466493 BuildFunctionPointerSignature ( funptr , trapFile , s => s . BuildDisplayName ( cx , trapFile ) ) ;
467494
495+ private static void BuildExtensionTypeDisplayName ( this INamedTypeSymbol named , Context cx , TextWriter trapFile )
496+ {
497+ trapFile . Write ( "extension(" ) ;
498+ if ( named . ExtensionParameter ? . Type is ITypeSymbol type )
499+ {
500+ type . BuildDisplayName ( cx , trapFile ) ;
501+ }
502+ else
503+ {
504+ trapFile . Write ( "unknown" ) ;
505+ }
506+ trapFile . Write ( ")" ) ;
507+ }
508+
468509 private static void BuildNamedTypeDisplayName ( this INamedTypeSymbol namedType , Context cx , TextWriter trapFile , bool constructUnderlyingTupleType )
469510 {
470511 if ( ! constructUnderlyingTupleType && namedType . IsTupleType )
@@ -484,6 +525,12 @@ private static void BuildNamedTypeDisplayName(this INamedTypeSymbol namedType, C
484525 return ;
485526 }
486527
528+ if ( namedType . IsExtension )
529+ {
530+ namedType . BuildExtensionTypeDisplayName ( cx , trapFile ) ;
531+ return ;
532+ }
533+
487534 if ( namedType . IsAnonymousType )
488535 {
489536 namedType . BuildAnonymousName ( cx , trapFile ) ;
@@ -596,6 +643,87 @@ public static bool IsSourceDeclaration(this IParameterSymbol parameter)
596643 return true ;
597644 }
598645
646+ /// <summary>
647+ /// Return true if this method is a compiler-generated extension method.
648+ /// </summary>
649+ public static bool IsCompilerGeneratedExtensionMethod ( this IMethodSymbol method ) =>
650+ method . TryGetExtensionMethod ( out _ ) ;
651+
652+ /// <summary>
653+ /// Returns true if this method is a compiler-generated extension method,
654+ /// and outputs the original extension method declaration.
655+ /// </summary>
656+ public static bool TryGetExtensionMethod ( this IMethodSymbol method , out IMethodSymbol ? declaration )
657+ {
658+ declaration = null ;
659+ if ( method . IsImplicitlyDeclared && method . ContainingSymbol is INamedTypeSymbol containingType )
660+ {
661+ // Extension types are declared within the same type as the generated
662+ // extension method implementation.
663+ var extensions = containingType . GetMembers ( )
664+ . OfType < INamedTypeSymbol > ( )
665+ . Where ( t => t . IsExtension ) ;
666+ // Find the (possibly unbound) original extension method that maps to this implementation (if any).
667+ var unboundDeclaration = extensions . SelectMany ( e => e . GetMembers ( ) )
668+ . OfType < IMethodSymbol > ( )
669+ . FirstOrDefault ( m => SymbolEqualityComparer . Default . Equals ( m . AssociatedExtensionImplementation , method . ConstructedFrom ) ) ;
670+
671+ var isFullyConstructed = method . IsBoundGenericMethod ( ) ;
672+ if ( isFullyConstructed && unboundDeclaration ? . ContainingType is INamedTypeSymbol extensionType )
673+ {
674+ try
675+ {
676+ // Use the type arguments from the constructed extension method to construct the extension type.
677+ var arguments = method . TypeArguments . ToArray ( ) ;
678+ var ( extensionTypeArguments , extensionMethodArguments ) = arguments . SplitAt ( extensionType . TypeParameters . Length ) ;
679+
680+ // Construct the extension type.
681+ var boundExtensionType = extensionType . IsUnboundGenericType ( )
682+ ? extensionType . Construct ( extensionTypeArguments . ToArray ( ) )
683+ : extensionType ;
684+
685+ // Find the extension method declaration within the constructed extension type.
686+ var extensionDeclaration = boundExtensionType . GetMembers ( )
687+ . OfType < IMethodSymbol > ( )
688+ . First ( c => SymbolEqualityComparer . Default . Equals ( c . OriginalDefinition , unboundDeclaration ) ) ;
689+
690+ // If the extension declaration is unbound apply the remaning type arguments and construct it.
691+ declaration = extensionDeclaration . IsUnboundGenericMethod ( )
692+ ? extensionDeclaration . Construct ( extensionMethodArguments . ToArray ( ) )
693+ : extensionDeclaration ;
694+ }
695+ catch
696+ {
697+ // If anything goes wrong, fall back to the unbound declaration.
698+ declaration = unboundDeclaration ;
699+ }
700+ }
701+ else
702+ {
703+ declaration = unboundDeclaration ;
704+ }
705+
706+ }
707+ return declaration is not null ;
708+ }
709+
710+ /// <summary>
711+ /// Returns true if this method is an unbound generic method.
712+ /// </summary>
713+ public static bool IsUnboundGenericMethod ( this IMethodSymbol method ) =>
714+ method . IsGenericMethod && SymbolEqualityComparer . Default . Equals ( method . ConstructedFrom , method ) ;
715+
716+ /// <summary>
717+ /// Returns true if this method is a bound generic method.
718+ /// </summary>
719+ public static bool IsBoundGenericMethod ( this IMethodSymbol method ) => method . IsGenericMethod && ! method . IsUnboundGenericMethod ( ) ;
720+
721+ /// <summary>
722+ /// Returns true if this type is an unbound generic type.
723+ /// </summary>
724+ public static bool IsUnboundGenericType ( this INamedTypeSymbol type ) =>
725+ type . IsGenericType && SymbolEqualityComparer . Default . Equals ( type . ConstructedFrom , type ) ;
726+
599727 /// <summary>
600728 /// Gets the base type of `symbol`. Unlike `symbol.BaseType`, this excludes effective base
601729 /// types of type parameters as well as `object` base types.
0 commit comments