@@ -35,6 +35,7 @@ private ImmutableArray<Symbol> BindCrefInternal(CrefSyntax syntax, out Symbol? a
3535 case SyntaxKind . IndexerMemberCref :
3636 case SyntaxKind . OperatorMemberCref :
3737 case SyntaxKind . ConversionOperatorMemberCref :
38+ case SyntaxKind . ExtensionMemberCref :
3839 return BindMemberCref ( ( MemberCrefSyntax ) syntax , containerOpt : null , ambiguityWinner : out ambiguityWinner , diagnostics : diagnostics ) ;
3940 default :
4041 throw ExceptionUtilities . UnexpectedValue ( syntax . Kind ( ) ) ;
@@ -125,6 +126,9 @@ private ImmutableArray<Symbol> BindMemberCref(MemberCrefSyntax syntax, Namespace
125126 case SyntaxKind . ConversionOperatorMemberCref :
126127 result = BindConversionOperatorMemberCref ( ( ConversionOperatorMemberCrefSyntax ) syntax , containerOpt , out ambiguityWinner , diagnostics ) ;
127128 break ;
129+ case SyntaxKind . ExtensionMemberCref :
130+ result = BindExtensionMemberCref ( ( ExtensionMemberCrefSyntax ) syntax , containerOpt , out ambiguityWinner , diagnostics ) ;
131+ break ;
128132 default :
129133 throw ExceptionUtilities . UnexpectedValue ( syntax . Kind ( ) ) ;
130134 }
@@ -216,6 +220,142 @@ private ImmutableArray<Symbol> BindIndexerMemberCref(IndexerMemberCrefSyntax syn
216220 diagnostics : diagnostics ) ;
217221 }
218222
223+ private ImmutableArray < Symbol > BindExtensionMemberCref ( ExtensionMemberCrefSyntax syntax , NamespaceOrTypeSymbol ? containerOpt , out Symbol ? ambiguityWinner , BindingDiagnosticBag diagnostics )
224+ {
225+ // Tracked by https://github.com/dotnet/roslyn/issues/76130 : handle extension operators
226+ CheckFeatureAvailability ( syntax , MessageID . IDS_FeatureExtensions , diagnostics ) ;
227+
228+ if ( containerOpt is not NamedTypeSymbol namedContainer )
229+ {
230+ ambiguityWinner = null ;
231+ return ImmutableArray < Symbol > . Empty ;
232+ }
233+
234+ ImmutableArray < Symbol > sortedSymbols = default ;
235+ int arity = 0 ;
236+ TypeArgumentListSyntax ? typeArgumentListSyntax = null ;
237+ CrefParameterListSyntax ? parameters = null ;
238+
239+ if ( syntax . Member is NameMemberCrefSyntax { Name : SimpleNameSyntax simpleName } nameMember )
240+ {
241+ arity = simpleName . Arity ;
242+ typeArgumentListSyntax = simpleName is GenericNameSyntax genericName ? genericName . TypeArgumentList : null ;
243+ parameters = nameMember . Parameters ;
244+
245+ TypeArgumentListSyntax ? extensionTypeArguments = syntax . TypeArgumentList ;
246+ int extensionArity = extensionTypeArguments ? . Arguments . Count ?? 0 ;
247+ sortedSymbols = computeSortedAndFilteredCrefExtensionMembers ( namedContainer , simpleName . Identifier . ValueText , extensionArity , arity , extensionTypeArguments , diagnostics , syntax ) ;
248+ }
249+
250+ if ( sortedSymbols . IsDefaultOrEmpty )
251+ {
252+ ambiguityWinner = null ;
253+ return [ ] ;
254+ }
255+
256+ Debug . Assert ( sortedSymbols . All ( s => s . GetIsNewExtensionMember ( ) ) ) ;
257+
258+ return ProcessCrefMemberLookupResults ( sortedSymbols , arity , syntax , typeArgumentListSyntax , parameters , out ambiguityWinner , diagnostics ) ;
259+
260+ ImmutableArray < Symbol > computeSortedAndFilteredCrefExtensionMembers ( NamedTypeSymbol container , string name , int extensionArity , int arity , TypeArgumentListSyntax ? extensionTypeArguments , BindingDiagnosticBag diagnostics , ExtensionMemberCrefSyntax syntax )
261+ {
262+ Debug . Assert ( name is not null ) ;
263+
264+ Debug . Assert ( syntax . Parameters is not null ) ;
265+ ImmutableArray < ParameterSymbol > extensionParameterSymbols = BindCrefParameters ( syntax . Parameters , diagnostics ) ;
266+
267+ // Use signature method symbols to match extension blocks
268+ var providedExtensionSignature = new SignatureOnlyMethodSymbol (
269+ methodKind : MethodKind . Ordinary ,
270+ typeParameters : IndexedTypeParameterSymbol . TakeSymbols ( extensionArity ) ,
271+ parameters : extensionParameterSymbols ,
272+ callingConvention : Cci . CallingConvention . Default ,
273+ // These are ignored by this specific MemberSignatureComparer.
274+ containingType : null ,
275+ name : null ,
276+ refKind : RefKind . None ,
277+ isInitOnly : false ,
278+ isStatic : false ,
279+ returnType : default ,
280+ refCustomModifiers : [ ] ,
281+ explicitInterfaceImplementations : [ ] ) ;
282+
283+ LookupOptions options = LookupOptions . AllMethodsOnArityZero | LookupOptions . MustNotBeParameter ;
284+ CompoundUseSiteInfo < AssemblySymbol > useSiteInfo = this . GetNewCompoundUseSiteInfo ( diagnostics ) ;
285+ ArrayBuilder < Symbol > ? sortedSymbolsBuilder = null ;
286+
287+ foreach ( var nested in container . GetTypeMembers ( ) )
288+ {
289+ if ( ! nested . IsExtension || nested . Arity != extensionArity || nested . ExtensionParameter is null )
290+ {
291+ continue ;
292+ }
293+
294+ var constructedNested = ( NamedTypeSymbol ) ConstructWithCrefTypeParameters ( extensionArity , extensionTypeArguments , nested ) ;
295+
296+ var candidateExtensionSignature = new SignatureOnlyMethodSymbol (
297+ methodKind : MethodKind . Ordinary ,
298+ typeParameters : IndexedTypeParameterSymbol . TakeSymbols ( constructedNested . Arity ) ,
299+ parameters : [ constructedNested . ExtensionParameter ] ,
300+ callingConvention : Cci . CallingConvention . Default ,
301+ // These are ignored by this specific MemberSignatureComparer.
302+ containingType : null ,
303+ name : null ,
304+ refKind : RefKind . None ,
305+ isInitOnly : false ,
306+ isStatic : false ,
307+ returnType : default ,
308+ refCustomModifiers : [ ] ,
309+ explicitInterfaceImplementations : [ ] ) ;
310+
311+ if ( ! MemberSignatureComparer . CrefComparer . Equals ( candidateExtensionSignature , providedExtensionSignature ) )
312+ {
313+ continue ;
314+ }
315+
316+ var candidates = constructedNested . GetMembers ( name ) ;
317+
318+ foreach ( var candidate in candidates )
319+ {
320+ if ( ! SourceMemberContainerTypeSymbol . IsAllowedExtensionMember ( candidate ) )
321+ {
322+ continue ;
323+ }
324+
325+ if ( arity != 0 && candidate . GetArity ( ) != arity )
326+ {
327+ continue ;
328+ }
329+
330+ // Note: we bypass the arity check here, as it would check for total arity (extension + member arity)
331+ SingleLookupResult result = this . CheckViability ( candidate , arity : 0 , options , accessThroughType : null , diagnose : true , useSiteInfo : ref useSiteInfo ) ;
332+
333+ if ( result . Kind == LookupResultKind . Viable )
334+ {
335+ sortedSymbolsBuilder ??= ArrayBuilder < Symbol > . GetInstance ( ) ;
336+ sortedSymbolsBuilder . Add ( result . Symbol ) ;
337+ }
338+ }
339+ }
340+
341+ diagnostics . Add ( syntax , useSiteInfo ) ;
342+
343+ if ( sortedSymbolsBuilder is null )
344+ {
345+ return ImmutableArray < Symbol > . Empty ;
346+ }
347+
348+ // Since we resolve ambiguities by just picking the first symbol we encounter,
349+ // the order of the symbols matters for repeatability.
350+ if ( sortedSymbolsBuilder . Count > 1 )
351+ {
352+ sortedSymbolsBuilder . Sort ( ConsistentSymbolOrder . Instance ) ;
353+ }
354+
355+ return sortedSymbolsBuilder . ToImmutableAndFree ( ) ;
356+ }
357+ }
358+
219359 // NOTE: not guaranteed to be a method (e.g. class op_Addition)
220360 // NOTE: constructor fallback logic applies
221361 private ImmutableArray < Symbol > BindOperatorMemberCref ( OperatorMemberCrefSyntax syntax , NamespaceOrTypeSymbol ? containerOpt , out Symbol ? ambiguityWinner , BindingDiagnosticBag diagnostics )
0 commit comments