@@ -135,10 +135,11 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
135135 considerName : true ,
136136 considerExplicitlyImplementedInterfaces : true ,
137137 considerReturnType : true ,
138- considerTypeConstraints : false ,
138+ considerTypeConstraints : false , // constraints are checked by caller instead
139139 considerCallingConvention : false ,
140140 refKindCompareMode : RefKindCompareMode . ConsiderDifferences ,
141- typeComparison : TypeCompareKind . ObliviousNullableModifierMatchesAny ) ;
141+ typeComparison : TypeCompareKind . ObliviousNullableModifierMatchesAny ,
142+ considerParameterNames : true ) ;
142143
143144 /// <summary>
144145 /// Determines if an interceptor has a compatible signature with an interceptable method.
@@ -354,6 +355,21 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
354355 considerDefaultValues : true ,
355356 typeComparison : TypeCompareKind . AllIgnoreOptions ) ;
356357
358+ /// <summary>
359+ /// This instance is used to determine if two extension blocks match in C# sense and thus are allowed to share a marker type.
360+ /// It considers parameter and type parameter names, type constraints, nullability and attributes.
361+ /// </summary>
362+ public static readonly MemberSignatureComparer ExtensionSignatureComparer = new MemberSignatureComparer (
363+ considerName : false ,
364+ considerExplicitlyImplementedInterfaces : false ,
365+ considerReturnType : false ,
366+ considerTypeConstraints : true ,
367+ considerCallingConvention : false ,
368+ refKindCompareMode : RefKindCompareMode . ConsiderDifferences ,
369+ typeComparison : TypeCompareKind . ConsiderEverything ,
370+ considerParameterNames : true ,
371+ considerAttributes : true ) ;
372+
357373 // Compare the "unqualified" part of the member name (no explicit part)
358374 private readonly bool _considerName ;
359375
@@ -380,6 +396,11 @@ internal sealed class MemberSignatureComparer : IEqualityComparer<Symbol>
380396 // Equality options for parameter types and return types (if return is considered).
381397 private readonly TypeCompareKind _typeComparison ;
382398
399+ // Compare parameter and type parameter names
400+ private readonly bool _considerParameterNames ;
401+
402+ private readonly bool _considerAttributes ;
403+
383404 private MemberSignatureComparer (
384405 bool considerName ,
385406 bool considerExplicitlyImplementedInterfaces ,
@@ -389,7 +410,9 @@ private MemberSignatureComparer(
389410 RefKindCompareMode refKindCompareMode ,
390411 bool considerArity = true ,
391412 bool considerDefaultValues = false ,
392- TypeCompareKind typeComparison = TypeCompareKind . IgnoreDynamic | TypeCompareKind . IgnoreNativeIntegers )
413+ TypeCompareKind typeComparison = TypeCompareKind . IgnoreDynamic | TypeCompareKind . IgnoreNativeIntegers ,
414+ bool considerParameterNames = false ,
415+ bool considerAttributes = false )
393416 {
394417 Debug . Assert ( ! considerExplicitlyImplementedInterfaces || considerName , "Doesn't make sense to consider interfaces separately from name." ) ;
395418 Debug . Assert ( ! considerTypeConstraints || considerArity , "If you consider type constraints, you must also consider arity" ) ;
@@ -412,6 +435,9 @@ private MemberSignatureComparer(
412435 {
413436 _typeComparison |= TypeCompareKind . FunctionPointerRefMatchesOutInRefReadonly ;
414437 }
438+
439+ _considerParameterNames = considerParameterNames ;
440+ _considerAttributes = considerAttributes ;
415441 }
416442
417443 #region IEqualityComparer<Symbol> Members
@@ -447,9 +473,28 @@ public bool Equals(Symbol? member1, Symbol? member2)
447473
448474 // NB: up to, and including, this check, we have not actually forced the (type) parameters
449475 // to be expanded - we're only using the counts.
450- if ( _considerArity && ( member1 . GetMemberArity ( ) != member2 . GetMemberArity ( ) ) )
476+ if ( _considerArity )
451477 {
452- return false ;
478+ if ( member1 . GetMemberArity ( ) != member2 . GetMemberArity ( ) )
479+ {
480+ return false ;
481+ }
482+
483+ if ( member1 . GetMemberArity ( ) > 0 && ( _considerParameterNames || _considerAttributes ) )
484+ {
485+ ImmutableArray < TypeParameterSymbol > typeParams1 = member1 . GetMemberTypeParameters ( ) ;
486+ ImmutableArray < TypeParameterSymbol > typeParams2 = member2 . GetMemberTypeParameters ( ) ;
487+
488+ if ( _considerParameterNames && typeParams1 . Length > 0 && ! haveSameTypeParameterNames ( typeParams1 , typeParams2 ) )
489+ {
490+ return false ;
491+ }
492+
493+ if ( _considerAttributes && ! haveSameTypeParameterAttributes ( typeParams1 , typeParams2 ) )
494+ {
495+ return false ;
496+ }
497+ }
453498 }
454499
455500 if ( member1 . GetParameterCount ( ) != member2 . GetParameterCount ( ) )
@@ -465,10 +510,25 @@ public bool Equals(Symbol? member1, Symbol? member2)
465510 return false ;
466511 }
467512
468- if ( member1 . GetParameterCount ( ) > 0 && ! HaveSameParameterTypes ( member1 . GetParameters ( ) . AsSpan ( ) , typeMap1 , member2 . GetParameters ( ) . AsSpan ( ) , typeMap2 ,
469- _refKindCompareMode , considerDefaultValues : _considerDefaultValues , _typeComparison ) )
513+ if ( member1 . GetParameterCount ( ) > 0 )
470514 {
471- return false ;
515+ var params1 = member1 . GetParameters ( ) ;
516+ var params2 = member2 . GetParameters ( ) ;
517+ if ( ! HaveSameParameterTypes ( params1 . AsSpan ( ) , typeMap1 , params2 . AsSpan ( ) , typeMap2 ,
518+ _refKindCompareMode , considerDefaultValues : _considerDefaultValues , _typeComparison ) )
519+ {
520+ return false ;
521+ }
522+
523+ if ( _considerParameterNames && ! haveSameParameterNames ( params1 , params2 ) )
524+ {
525+ return false ;
526+ }
527+
528+ if ( _considerAttributes && ! haveSameParameterAttributes ( params1 , params2 ) )
529+ {
530+ return false ;
531+ }
472532 }
473533
474534 if ( _considerCallingConvention )
@@ -526,7 +586,63 @@ public bool Equals(Symbol? member1, Symbol? member2)
526586 }
527587 }
528588
529- return ! _considerTypeConstraints || HaveSameConstraints ( member1 , typeMap1 , member2 , typeMap2 ) ;
589+ return ! _considerTypeConstraints || HaveSameConstraints ( member1 , typeMap1 , member2 , typeMap2 , ignoreNullability : ( _typeComparison & TypeCompareKind . IgnoreNullableModifiersForReferenceTypes ) != 0 ) ;
590+
591+ static bool haveSameParameterNames ( ImmutableArray < ParameterSymbol > params1 , ImmutableArray < ParameterSymbol > params2 )
592+ {
593+ return params1 . SequenceEqual ( params2 , ( p1 , p2 ) => p1 . Name == p2 . Name ) ;
594+ }
595+
596+ static bool haveSameTypeParameterNames ( ImmutableArray < TypeParameterSymbol > typeParams1 , ImmutableArray < TypeParameterSymbol > typeParams2 )
597+ {
598+ return typeParams1 . SequenceEqual ( typeParams2 , ( p1 , p2 ) => p1 . Name == p2 . Name ) ;
599+ }
600+
601+ static bool haveSameParameterAttributes ( ImmutableArray < ParameterSymbol > params1 , ImmutableArray < ParameterSymbol > params2 )
602+ {
603+ return params1 . SequenceEqual ( params2 , ( p1 , p2 ) => hasSameAttribute ( p1 . GetAttributes ( ) , p2 . GetAttributes ( ) ) ) ;
604+ }
605+
606+ static bool haveSameTypeParameterAttributes ( ImmutableArray < TypeParameterSymbol > typeParams1 , ImmutableArray < TypeParameterSymbol > typeParams2 )
607+ {
608+ return typeParams1 . SequenceEqual ( typeParams2 , ( p1 , p2 ) => hasSameAttribute ( p1 . GetAttributes ( ) , p2 . GetAttributes ( ) ) ) ;
609+ }
610+
611+ static bool hasSameAttribute ( ImmutableArray < CSharpAttributeData > attributes1 , ImmutableArray < CSharpAttributeData > attributes2 )
612+ {
613+ if ( attributes1 . Length != attributes2 . Length )
614+ {
615+ return false ;
616+ }
617+
618+ var comparer = CommonAttributeDataComparer . InstanceIgnoringNamedArgumentOrder ;
619+
620+ if ( attributes1 is [ var single1 ] && attributes2 is [ var single2 ] )
621+ {
622+ // Fast path for single attribute case
623+ return comparer . Equals ( single1 , single2 ) ;
624+ }
625+
626+ // Tracked by https://github.com/dotnet/roslyn/issues/78827 : optimization, consider using a pool
627+ var counts = new Dictionary < CSharpAttributeData , int > ( comparer ) ;
628+
629+ foreach ( var attribute in attributes1 )
630+ {
631+ counts [ attribute ] = counts . TryGetValue ( attribute , out var foundCount ) ? foundCount + 1 : 1 ;
632+ }
633+
634+ foreach ( var attribute in attributes2 )
635+ {
636+ if ( ! counts . TryGetValue ( attribute , out var foundCount ) || foundCount == 0 )
637+ {
638+ return false ;
639+ }
640+
641+ counts [ attribute ] = foundCount - 1 ;
642+ }
643+
644+ return counts . Values . All ( c => c == 0 ) ;
645+ }
530646 }
531647
532648 public int GetHashCode ( Symbol ? member )
@@ -623,7 +739,7 @@ public static bool HaveSameReturnTypes(Symbol member1, TypeMap? typeMap1, Symbol
623739 true ) ;
624740 }
625741
626- private static bool HaveSameConstraints ( Symbol member1 , TypeMap ? typeMap1 , Symbol member2 , TypeMap ? typeMap2 )
742+ private static bool HaveSameConstraints ( Symbol member1 , TypeMap ? typeMap1 , Symbol member2 , TypeMap ? typeMap2 , bool ignoreNullability = true )
627743 {
628744 Debug . Assert ( member1 . GetMemberArity ( ) == member2 . GetMemberArity ( ) ) ;
629745
@@ -635,17 +751,17 @@ private static bool HaveSameConstraints(Symbol member1, TypeMap? typeMap1, Symbo
635751
636752 var typeParameters1 = member1 . GetMemberTypeParameters ( ) ;
637753 var typeParameters2 = member2 . GetMemberTypeParameters ( ) ;
638- return HaveSameConstraints ( typeParameters1 , typeMap1 , typeParameters2 , typeMap2 ) ;
754+ return HaveSameConstraints ( typeParameters1 , typeMap1 , typeParameters2 , typeMap2 , ignoreNullability ) ;
639755 }
640756
641- public static bool HaveSameConstraints ( ImmutableArray < TypeParameterSymbol > typeParameters1 , TypeMap ? typeMap1 , ImmutableArray < TypeParameterSymbol > typeParameters2 , TypeMap ? typeMap2 )
757+ public static bool HaveSameConstraints ( ImmutableArray < TypeParameterSymbol > typeParameters1 , TypeMap ? typeMap1 , ImmutableArray < TypeParameterSymbol > typeParameters2 , TypeMap ? typeMap2 , bool ignoreNullability = true )
642758 {
643759 Debug . Assert ( typeParameters1 . Length == typeParameters2 . Length ) ;
644760
645761 int arity = typeParameters1 . Length ;
646762 for ( int i = 0 ; i < arity ; i ++ )
647763 {
648- if ( ! HaveSameConstraints ( typeParameters1 [ i ] , typeMap1 , typeParameters2 [ i ] , typeMap2 ) )
764+ if ( ! HaveSameConstraints ( typeParameters1 [ i ] , typeMap1 , typeParameters2 [ i ] , typeMap2 , ignoreNullability ) )
649765 {
650766 return false ;
651767 }
@@ -654,10 +770,16 @@ public static bool HaveSameConstraints(ImmutableArray<TypeParameterSymbol> typeP
654770 return true ;
655771 }
656772
657- public static bool HaveSameConstraints ( TypeParameterSymbol typeParameter1 , TypeMap ? typeMap1 , TypeParameterSymbol typeParameter2 , TypeMap ? typeMap2 )
773+ public static bool HaveSameConstraints ( TypeParameterSymbol typeParameter1 , TypeMap ? typeMap1 , TypeParameterSymbol typeParameter2 , TypeMap ? typeMap2 , bool ignoreNullability = true )
658774 {
659775 // Spec 13.4.3: Implementation of generic methods.
660776
777+ if ( ! ignoreNullability &&
778+ typeParameter1 . HasNotNullConstraint != typeParameter2 . HasNotNullConstraint )
779+ {
780+ return false ;
781+ }
782+
661783 if ( ( typeParameter1 . HasConstructorConstraint != typeParameter2 . HasConstructorConstraint ) ||
662784 ( typeParameter1 . HasReferenceTypeConstraint != typeParameter2 . HasReferenceTypeConstraint ) ||
663785 ( typeParameter1 . HasValueTypeConstraint != typeParameter2 . HasValueTypeConstraint ) ||
@@ -903,7 +1025,7 @@ internal static bool ConsideringTupleNamesCreatesDifference(Symbol member1, Symb
9031025 internal enum RefKindCompareMode
9041026 {
9051027 /// <summary>
906- /// Ref parameter modifiers are ignored .
1028+ /// All ref modifiers are considered equivalent .
9071029 /// </summary>
9081030 DoNotConsiderDifferences = 0 ,
9091031
0 commit comments