@@ -78,10 +78,12 @@ static bool HasIndexedConflict(string @base, string parameterName, int count)
7878 }
7979 }
8080
81- // A member (or accessor) declared in another assembly is overridable only if the overriding
82- // assembly can actually see it. `internal` and `private protected` are invisible across assembly
83- // boundaries unless the declaring assembly grants InternalsVisibleTo. `protected internal`
84- // (= protected OR internal) is always reachable via the protected half from a derived class.
81+ /// <summary>
82+ /// A member (or accessor) declared in another assembly is overridable only if the overriding
83+ /// assembly can actually see it. `internal` and `private protected` are invisible across assembly
84+ /// boundaries unless the declaring assembly grants InternalsVisibleTo. `protected internal`
85+ /// (= protected OR internal) is always reachable via the protected half from a derived class.
86+ /// </summary>
8587 public static bool IsOverridableFrom ( ISymbol member , IAssemblySymbol ? sourceAssembly )
8688 {
8789 if ( sourceAssembly is null ||
@@ -99,6 +101,41 @@ member.DeclaredAccessibility is not (Accessibility.Internal or Accessibility.Pro
99101 return containingAssembly . GivesAccessTo ( sourceAssembly ) ;
100102 }
101103
104+ /// <summary>
105+ /// C# requires an override to match the base member's declared accessibility, with one
106+ /// exception: when overriding a `protected internal` member from an assembly that cannot see
107+ /// `internal` (i.e. neither the same assembly nor an InternalsVisibleTo target), the override
108+ /// must drop the internal half and use plain `protected`.
109+ /// </summary>
110+ public static string ResolveOverrideVisibility ( Accessibility accessibility ,
111+ IAssemblySymbol ? containingAssembly , IAssemblySymbol ? sourceAssembly )
112+ => accessibility switch
113+ {
114+ Accessibility . Public => "public" ,
115+ Accessibility . Protected => "protected" ,
116+ Accessibility . Internal => "internal" ,
117+ Accessibility . ProtectedAndInternal => "private protected" ,
118+ Accessibility . ProtectedOrInternal => HasInternalAccess ( containingAssembly , sourceAssembly )
119+ ? "protected internal"
120+ : "protected" ,
121+ _ => "private" ,
122+ } ;
123+
124+ private static bool HasInternalAccess ( IAssemblySymbol ? containingAssembly , IAssemblySymbol ? sourceAssembly )
125+ {
126+ if ( sourceAssembly is null || containingAssembly is null )
127+ {
128+ return false ;
129+ }
130+
131+ if ( SymbolEqualityComparer . Default . Equals ( containingAssembly , sourceAssembly ) )
132+ {
133+ return true ;
134+ }
135+
136+ return containingAssembly . GivesAccessTo ( sourceAssembly ) ;
137+ }
138+
102139 public static string ToTypeOrWrapper ( this Type type )
103140 {
104141 if ( type . SpecialGenericType == SpecialGenericType . Span )
@@ -146,8 +183,8 @@ public static bool NeedsRefStructPipeline(this MethodParameter parameter)
146183 }
147184
148185 return parameter . RefKind == RefKind . RefReadOnlyParameter
149- && parameter . Type . IsRefStruct
150- && parameter . Type . SpecialGenericType is ( SpecialGenericType . Span or SpecialGenericType . ReadOnlySpan ) ;
186+ && parameter . Type . IsRefStruct
187+ && parameter . Type . SpecialGenericType is SpecialGenericType . Span or SpecialGenericType . ReadOnlySpan ;
151188 }
152189
153190 extension ( ITypeSymbol typeSymbol )
0 commit comments