@@ -354,6 +354,21 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
354354 considerDefaultValues : true ,
355355 typeComparison : TypeCompareKind . AllIgnoreOptions ) ;
356356
357+ /// <summary>
358+ /// This instance is used to determine if two extension blocks match in C# sense and thus are allowed to share a marker type.
359+ /// It considers parameter and type parameter names, type constraints, nullability and attributes.
360+ /// </summary>
361+ public static readonly MemberSignatureComparer ExtensionSignatureComparer = new MemberSignatureComparer (
362+ considerName : false ,
363+ considerExplicitlyImplementedInterfaces : false ,
364+ considerReturnType : false ,
365+ considerTypeConstraints : true ,
366+ considerCallingConvention : false ,
367+ refKindCompareMode : RefKindCompareMode . ConsiderDifferences ,
368+ typeComparison : TypeCompareKind . ConsiderEverything ,
369+ considerParameterNames : true ,
370+ considerAttributes : true ) ;
371+
357372 // Compare the "unqualified" part of the member name (no explicit part)
358373 private readonly bool _considerName ;
359374
@@ -380,6 +395,11 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
380395 // Equality options for parameter types and return types (if return is considered).
381396 private readonly TypeCompareKind _typeComparison ;
382397
398+ // Compare parameter and type parameter names
399+ private readonly bool _considerParameterNames ;
400+
401+ private readonly bool _considerAttributes ;
402+
383403 private MemberSignatureComparer (
384404 bool considerName ,
385405 bool considerExplicitlyImplementedInterfaces ,
@@ -389,7 +409,9 @@ private MemberSignatureComparer(
389409 RefKindCompareMode refKindCompareMode ,
390410 bool considerArity = true ,
391411 bool considerDefaultValues = false ,
392- TypeCompareKind typeComparison = TypeCompareKind . IgnoreDynamic | TypeCompareKind . IgnoreNativeIntegers )
412+ TypeCompareKind typeComparison = TypeCompareKind . IgnoreDynamic | TypeCompareKind . IgnoreNativeIntegers ,
413+ bool considerParameterNames = false ,
414+ bool considerAttributes = false )
393415 {
394416 Debug . Assert ( ! considerExplicitlyImplementedInterfaces || considerName , "Doesn't make sense to consider interfaces separately from name." ) ;
395417 Debug . Assert ( ! considerTypeConstraints || considerArity , "If you consider type constraints, you must also consider arity" ) ;
@@ -412,6 +434,9 @@ private MemberSignatureComparer(
412434 {
413435 _typeComparison |= TypeCompareKind . FunctionPointerRefMatchesOutInRefReadonly ;
414436 }
437+
438+ _considerParameterNames = considerParameterNames ;
439+ _considerAttributes = considerAttributes ;
415440 }
416441
417442 #region IEqualityComparer<Symbol> Members
@@ -447,9 +472,28 @@ public bool Equals(Symbol? member1, Symbol? member2)
447472
448473 // NB: up to, and including, this check, we have not actually forced the (type) parameters
449474 // to be expanded - we're only using the counts.
450- if ( _considerArity && ( member1 . GetMemberArity ( ) != member2 . GetMemberArity ( ) ) )
475+ if ( _considerArity )
451476 {
452- return false ;
477+ if ( member1 . GetMemberArity ( ) != member2 . GetMemberArity ( ) )
478+ {
479+ return false ;
480+ }
481+
482+ if ( member1 . GetMemberArity ( ) > 0 && ( _considerParameterNames || _considerAttributes ) )
483+ {
484+ ImmutableArray < TypeParameterSymbol > typeParams1 = member1 . GetMemberTypeParameters ( ) ;
485+ ImmutableArray < TypeParameterSymbol > typeParams2 = member2 . GetMemberTypeParameters ( ) ;
486+
487+ if ( _considerParameterNames && typeParams1 . Length > 0 && ! haveSameTypeParameterNames ( typeParams1 , typeParams2 ) )
488+ {
489+ return false ;
490+ }
491+
492+ if ( _considerAttributes && ! haveSameTypeParameterAttributes ( typeParams1 , typeParams2 ) )
493+ {
494+ return false ;
495+ }
496+ }
453497 }
454498
455499 if ( member1 . GetParameterCount ( ) != member2 . GetParameterCount ( ) )
@@ -465,10 +509,25 @@ public bool Equals(Symbol? member1, Symbol? member2)
465509 return false ;
466510 }
467511
468- if ( member1 . GetParameterCount ( ) > 0 && ! HaveSameParameterTypes ( member1 . GetParameters ( ) . AsSpan ( ) , typeMap1 , member2 . GetParameters ( ) . AsSpan ( ) , typeMap2 ,
469- _refKindCompareMode , considerDefaultValues : _considerDefaultValues , _typeComparison ) )
512+ if ( member1 . GetParameterCount ( ) > 0 )
470513 {
471- return false ;
514+ var params1 = member1 . GetParameters ( ) ;
515+ var params2 = member2 . GetParameters ( ) ;
516+ if ( ! HaveSameParameterTypes ( params1 . AsSpan ( ) , typeMap1 , params2 . AsSpan ( ) , typeMap2 ,
517+ _refKindCompareMode , considerDefaultValues : _considerDefaultValues , _typeComparison ) )
518+ {
519+ return false ;
520+ }
521+
522+ if ( _considerParameterNames && ! haveSameParameterNames ( params1 , params2 ) )
523+ {
524+ return false ;
525+ }
526+
527+ if ( _considerAttributes && ! haveSameParameterAttributes ( params1 , params2 ) )
528+ {
529+ return false ;
530+ }
472531 }
473532
474533 if ( _considerCallingConvention )
@@ -526,7 +585,63 @@ public bool Equals(Symbol? member1, Symbol? member2)
526585 }
527586 }
528587
529- return ! _considerTypeConstraints || HaveSameConstraints ( member1 , typeMap1 , member2 , typeMap2 ) ;
588+ return ! _considerTypeConstraints || HaveSameConstraints ( member1 , typeMap1 , member2 , typeMap2 , ignoreNullability : ( _typeComparison & TypeCompareKind . IgnoreNullableModifiersForReferenceTypes ) != 0 ) ;
589+
590+ static bool haveSameParameterNames ( ImmutableArray < ParameterSymbol > params1 , ImmutableArray < ParameterSymbol > params2 )
591+ {
592+ return params1 . SequenceEqual ( params2 , ( p1 , p2 ) => p1 . Name == p2 . Name ) ;
593+ }
594+
595+ static bool haveSameTypeParameterNames ( ImmutableArray < TypeParameterSymbol > typeParams1 , ImmutableArray < TypeParameterSymbol > typeParams2 )
596+ {
597+ return typeParams1 . SequenceEqual ( typeParams2 , ( p1 , p2 ) => p1 . Name == p2 . Name ) ;
598+ }
599+
600+ static bool haveSameParameterAttributes ( ImmutableArray < ParameterSymbol > params1 , ImmutableArray < ParameterSymbol > params2 )
601+ {
602+ return params1 . SequenceEqual ( params2 , ( p1 , p2 ) => hasSameAttribute ( p1 . GetAttributes ( ) , p2 . GetAttributes ( ) ) ) ;
603+ }
604+
605+ static bool haveSameTypeParameterAttributes ( ImmutableArray < TypeParameterSymbol > typeParams1 , ImmutableArray < TypeParameterSymbol > typeParams2 )
606+ {
607+ return typeParams1 . SequenceEqual ( typeParams2 , ( p1 , p2 ) => hasSameAttribute ( p1 . GetAttributes ( ) , p2 . GetAttributes ( ) ) ) ;
608+ }
609+
610+ static bool hasSameAttribute ( ImmutableArray < CSharpAttributeData > attributes1 , ImmutableArray < CSharpAttributeData > attributes2 )
611+ {
612+ if ( attributes1 . Length != attributes2 . Length )
613+ {
614+ return false ;
615+ }
616+
617+ var comparer = CommonAttributeDataComparer . InstanceIgnoringNamedArgumentOrder ;
618+
619+ if ( attributes1 is [ var single1 ] && attributes2 is [ var single2 ] )
620+ {
621+ // Fast path for single attribute case
622+ return comparer . Equals ( single1 , single2 ) ;
623+ }
624+
625+ // Tracked by https://github.com/dotnet/roslyn/issues/78827 : optimization, consider using a pool
626+ var counts = new Dictionary < CSharpAttributeData , int > ( comparer ) ;
627+
628+ foreach ( var attribute in attributes1 )
629+ {
630+ counts [ attribute ] = counts . TryGetValue ( attribute , out var foundCount ) ? foundCount + 1 : 1 ;
631+ }
632+
633+ foreach ( var attribute in attributes2 )
634+ {
635+ if ( ! counts . TryGetValue ( attribute , out var foundCount ) || foundCount == 0 )
636+ {
637+ return false ;
638+ }
639+
640+ counts [ attribute ] = foundCount - 1 ;
641+ }
642+
643+ return counts . Values . All ( c => c == 0 ) ;
644+ }
530645 }
531646
532647 public int GetHashCode ( Symbol ? member )
@@ -623,7 +738,7 @@ public static bool HaveSameReturnTypes(Symbol member1, TypeMap? typeMap1, Symbol
623738 true ) ;
624739 }
625740
626- private static bool HaveSameConstraints ( Symbol member1 , TypeMap ? typeMap1 , Symbol member2 , TypeMap ? typeMap2 )
741+ private static bool HaveSameConstraints ( Symbol member1 , TypeMap ? typeMap1 , Symbol member2 , TypeMap ? typeMap2 , bool ignoreNullability = true )
627742 {
628743 Debug . Assert ( member1 . GetMemberArity ( ) == member2 . GetMemberArity ( ) ) ;
629744
@@ -635,17 +750,17 @@ private static bool HaveSameConstraints(Symbol member1, TypeMap? typeMap1, Symbo
635750
636751 var typeParameters1 = member1 . GetMemberTypeParameters ( ) ;
637752 var typeParameters2 = member2 . GetMemberTypeParameters ( ) ;
638- return HaveSameConstraints ( typeParameters1 , typeMap1 , typeParameters2 , typeMap2 ) ;
753+ return HaveSameConstraints ( typeParameters1 , typeMap1 , typeParameters2 , typeMap2 , ignoreNullability ) ;
639754 }
640755
641- public static bool HaveSameConstraints ( ImmutableArray < TypeParameterSymbol > typeParameters1 , TypeMap ? typeMap1 , ImmutableArray < TypeParameterSymbol > typeParameters2 , TypeMap ? typeMap2 )
756+ public static bool HaveSameConstraints ( ImmutableArray < TypeParameterSymbol > typeParameters1 , TypeMap ? typeMap1 , ImmutableArray < TypeParameterSymbol > typeParameters2 , TypeMap ? typeMap2 , bool ignoreNullability = true )
642757 {
643758 Debug . Assert ( typeParameters1 . Length == typeParameters2 . Length ) ;
644759
645760 int arity = typeParameters1 . Length ;
646761 for ( int i = 0 ; i < arity ; i ++ )
647762 {
648- if ( ! HaveSameConstraints ( typeParameters1 [ i ] , typeMap1 , typeParameters2 [ i ] , typeMap2 ) )
763+ if ( ! HaveSameConstraints ( typeParameters1 [ i ] , typeMap1 , typeParameters2 [ i ] , typeMap2 , ignoreNullability ) )
649764 {
650765 return false ;
651766 }
@@ -654,10 +769,16 @@ public static bool HaveSameConstraints(ImmutableArray<TypeParameterSymbol> typeP
654769 return true ;
655770 }
656771
657- public static bool HaveSameConstraints ( TypeParameterSymbol typeParameter1 , TypeMap ? typeMap1 , TypeParameterSymbol typeParameter2 , TypeMap ? typeMap2 )
772+ public static bool HaveSameConstraints ( TypeParameterSymbol typeParameter1 , TypeMap ? typeMap1 , TypeParameterSymbol typeParameter2 , TypeMap ? typeMap2 , bool ignoreNullability = true )
658773 {
659774 // Spec 13.4.3: Implementation of generic methods.
660775
776+ if ( ! ignoreNullability &&
777+ typeParameter1 . HasNotNullConstraint != typeParameter2 . HasNotNullConstraint )
778+ {
779+ return false ;
780+ }
781+
661782 if ( ( typeParameter1 . HasConstructorConstraint != typeParameter2 . HasConstructorConstraint ) ||
662783 ( typeParameter1 . HasReferenceTypeConstraint != typeParameter2 . HasReferenceTypeConstraint ) ||
663784 ( typeParameter1 . HasValueTypeConstraint != typeParameter2 . HasValueTypeConstraint ) ||
@@ -903,7 +1024,7 @@ internal static bool ConsideringTupleNamesCreatesDifference(Symbol member1, Symb
9031024 internal enum RefKindCompareMode
9041025 {
9051026 /// <summary>
906- /// Ref parameter modifiers are ignored .
1027+ /// All ref modifiers are considered equivalent .
9071028 /// </summary>
9081029 DoNotConsiderDifferences = 0 ,
9091030
0 commit comments