Skip to content

Commit 34c2420

Browse files
committed
Extensions: interceptors
1 parent 66b9922 commit 34c2420

File tree

2 files changed

+1035
-31
lines changed

2 files changed

+1035
-31
lines changed

src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Call.cs

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ private void InterceptCallAndAdjustArguments(
141141
bool invokedAsExtensionMethod,
142142
Syntax.SimpleNameSyntax? nameSyntax)
143143
{
144+
// Note: the extension method rewriter comes after the local rewriter, so we're still dealing with skeleton methods here
144145
if (this._compilation.TryGetInterceptor(nameSyntax) is not var (attributeLocation, interceptor))
145146
{
146147
// The call was not intercepted.
@@ -199,19 +200,23 @@ private void InterceptCallAndAdjustArguments(
199200
// When the original call is to an instance method, and the interceptor is an extension method,
200201
// we need to take special care to intercept with the extension method as though it is being called in reduced form.
201202
Debug.Assert(receiverOpt is not BoundTypeExpression || method.IsStatic);
202-
var needToReduce = receiverOpt is not (null or BoundTypeExpression) && interceptor.IsExtensionMethod; // Tracked by https://github.com/dotnet/roslyn/issues/76130: Test this code path with new extensions
203-
var symbolForCompare = needToReduce ? ReducedExtensionMethodSymbol.Create(interceptor, receiverOpt!.Type, _compilation, out _) : interceptor; // Tracked by https://github.com/dotnet/roslyn/issues/76130 : test interceptors
203+
bool receiverIsValue = receiverOpt is not (null or BoundTypeExpression);
204+
bool needToReduceInterceptor = receiverIsValue && interceptor.IsExtensionMethod;
204205

205-
if (!MemberSignatureComparer.InterceptorsComparer.Equals(method, symbolForCompare))
206+
MethodSymbol? interceptorForCompare =
207+
interceptor.GetIsNewExtensionMember() && invokedAsExtensionMethod ? interceptor.TryGetCorrespondingExtensionImplementationMethod() :
208+
needToReduceInterceptor ? ReducedExtensionMethodSymbol.Create(interceptor, receiverOpt!.Type, _compilation, out _) : interceptor;
209+
210+
if (!MemberSignatureComparer.InterceptorsComparer.Equals(method, interceptorForCompare))
206211
{
207-
this._diagnostics.Add(ErrorCode.ERR_InterceptorSignatureMismatch, attributeLocation, method, interceptor);
212+
this._diagnostics.Add(ErrorCode.ERR_InterceptorSignatureMismatch, attributeLocation, method, interceptor); // Consider printing the return types as part of the compared signatures with FormattedSymbol
208213
return;
209214
}
210215

211216
_ = SourceMemberContainerTypeSymbol.CheckValidNullableMethodOverride(
212217
_compilation,
213218
method,
214-
symbolForCompare,
219+
interceptorForCompare,
215220
_diagnostics,
216221
static (diagnostics, method, interceptor, topLevel, attributeLocation) =>
217222
{
@@ -223,20 +228,20 @@ private void InterceptCallAndAdjustArguments(
223228
},
224229
extraArgument: attributeLocation);
225230

226-
if (!MemberSignatureComparer.InterceptorsStrictComparer.Equals(method, symbolForCompare))
231+
if (!MemberSignatureComparer.InterceptorsStrictComparer.Equals(method, interceptorForCompare))
227232
{
228233
this._diagnostics.Add(ErrorCode.WRN_InterceptorSignatureMismatch, attributeLocation, method, interceptor);
229234
}
230235

231-
method.TryGetThisParameter(out var methodThisParameter);
232-
var interceptorThisParameterForCompare = needToReduce ? interceptor.Parameters[0] :
233-
interceptor.TryGetThisParameter(out var interceptorThisParameter) ? interceptorThisParameter : null;
234-
switch (methodThisParameter, interceptorThisParameterForCompare)
236+
ParameterSymbol? methodReceiverParameter = getReceiverParameter(method, classicExtensionNotInvokedAsStatic: invokedAsExtensionMethod);
237+
ParameterSymbol? interceptorThisParameterForCompare = getReceiverParameter(interceptor, classicExtensionNotInvokedAsStatic: invokedAsExtensionMethod || needToReduceInterceptor);
238+
239+
switch (methodReceiverParameter, interceptorThisParameterForCompare)
235240
{
236241
case (not null, null):
237-
case (not null, not null) when !methodThisParameter.Type.Equals(interceptorThisParameterForCompare.Type, TypeCompareKind.ObliviousNullableModifierMatchesAny)
238-
|| methodThisParameter.RefKind != interceptorThisParameterForCompare.RefKind:
239-
this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, methodThisParameter, method);
242+
case (not null, not null) when !methodReceiverParameter.Type.Equals(interceptorThisParameterForCompare.Type, TypeCompareKind.ObliviousNullableModifierMatchesAny)
243+
|| methodReceiverParameter.RefKind != interceptorThisParameterForCompare.RefKind:
244+
this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, methodReceiverParameter, method);
240245
return;
241246
case (null, not null):
242247
this._diagnostics.Add(ErrorCode.ERR_InterceptorMustNotHaveThisParameter, attributeLocation, method);
@@ -245,7 +250,7 @@ private void InterceptCallAndAdjustArguments(
245250
break;
246251
}
247252

248-
if (invokedAsExtensionMethod && interceptor.IsStatic && !interceptor.IsExtensionMethod) // Tracked by https://github.com/dotnet/roslyn/issues/76130: Test this code path with new extensions
253+
if (invokedAsExtensionMethod && interceptor.IsStatic && !interceptor.IsExtensionMethod)
249254
{
250255
// Special case when intercepting an extension method call in reduced form with a non-extension.
251256
this._diagnostics.Add(ErrorCode.ERR_InterceptorMustHaveMatchingThisParameter, attributeLocation, method.Parameters[0], method);
@@ -254,7 +259,7 @@ private void InterceptCallAndAdjustArguments(
254259

255260
if (SourceMemberContainerTypeSymbol.CheckValidScopedOverride(
256261
method,
257-
symbolForCompare,
262+
interceptorForCompare,
258263
this._diagnostics,
259264
static (diagnostics, method, symbolForCompare, implementingParameter, blameAttributes, attributeLocation) =>
260265
{
@@ -268,20 +273,21 @@ private void InterceptCallAndAdjustArguments(
268273
return;
269274
}
270275

271-
if (needToReduce)
276+
if (receiverIsValue && (interceptor.IsExtensionMethod || interceptor.GetIsNewExtensionMember()))
272277
{
273-
Debug.Assert(methodThisParameter is not null);
278+
Debug.Assert(methodReceiverParameter is not null);
274279
Debug.Assert(receiverOpt?.Type is not null);
275280

276281
// Usually we expect the receiver to already be converted to the this parameter type.
277282
// However, in the case of a non-reference type receiver, where the this parameter is some base reference type,
278283
// for example a struct type and System.ValueType respectively, we need to convert the receiver to parameter type,
279284
// because we can't use the same `.constrained` calling pattern here which we would have used for an instance method receiver.
280-
Debug.Assert(receiverOpt.Type.Equals(interceptor.Parameters[0].Type, TypeCompareKind.AllIgnoreOptions)
281-
|| (!receiverOpt.Type.IsReferenceType && interceptor.Parameters[0].Type.IsReferenceType));
282-
receiverOpt = MakeConversionNode(receiverOpt, interceptor.Parameters[0].Type, @checked: false, markAsChecked: true);
285+
Debug.Assert(receiverOpt.Type.Equals(interceptorThisParameterForCompare!.Type, TypeCompareKind.AllIgnoreOptions)
286+
|| (!receiverOpt.Type.IsReferenceType && interceptorThisParameterForCompare.Type.IsReferenceType));
287+
288+
receiverOpt = MakeConversionNode(receiverOpt, interceptorThisParameterForCompare.Type, @checked: false, markAsChecked: true);
283289

284-
var thisRefKind = methodThisParameter.RefKind;
290+
var thisRefKind = methodReceiverParameter.RefKind;
285291
// Instance call receivers can be implicitly captured to temps in the emit layer, but not static call arguments
286292
// Therefore we may need to explicitly store the receiver to temp here.
287293
if (thisRefKind != RefKind.None
@@ -297,24 +303,49 @@ private void InterceptCallAndAdjustArguments(
297303
receiverOpt = _factory.Sequence(locals: [], sideEffects: [assignmentToTemp], receiverTemp);
298304
}
299305

300-
arguments = arguments.Insert(0, receiverOpt);
301-
receiverOpt = null;
302-
303-
// CodeGenerator.EmitArguments requires that we have a fully-filled-out argumentRefKindsOpt for any ref/in/out arguments.
304-
if (argumentRefKindsOpt.IsDefault && thisRefKind != RefKind.None)
306+
if (interceptor.IsExtensionMethod)
305307
{
306-
argumentRefKindsOpt = method.Parameters.SelectAsArray(static param => param.RefKind);
307-
}
308+
arguments = arguments.Insert(0, receiverOpt);
309+
receiverOpt = null;
308310

309-
if (!argumentRefKindsOpt.IsDefault)
310-
{
311-
argumentRefKindsOpt = argumentRefKindsOpt.Insert(0, thisRefKind);
311+
// CodeGenerator.EmitArguments requires that we have a fully-filled-out argumentRefKindsOpt for any ref/in/out arguments.
312+
if (argumentRefKindsOpt.IsDefault && thisRefKind != RefKind.None)
313+
{
314+
argumentRefKindsOpt = method.Parameters.SelectAsArray(static param => param.RefKind);
315+
}
316+
317+
if (!argumentRefKindsOpt.IsDefault)
318+
{
319+
argumentRefKindsOpt = argumentRefKindsOpt.Insert(0, thisRefKind);
320+
}
312321
}
313322
}
314323

315324
method = interceptor;
316325

317326
return;
327+
328+
static ParameterSymbol? getReceiverParameter(MethodSymbol method, bool classicExtensionNotInvokedAsStatic)
329+
{
330+
if (method.IsExtensionMethod && classicExtensionNotInvokedAsStatic)
331+
{
332+
return method.Parameters[0];
333+
}
334+
335+
if (method.GetIsNewExtensionMember())
336+
{
337+
if (method.IsStatic)
338+
{
339+
return null;
340+
}
341+
342+
Debug.Assert(method.ContainingType.ExtensionParameter is not null);
343+
return method.ContainingType.ExtensionParameter;
344+
}
345+
346+
method.TryGetThisParameter(out ParameterSymbol? methodReceiverParameter);
347+
return methodReceiverParameter;
348+
}
318349
}
319350

320351
public override BoundNode VisitCall(BoundCall node)

0 commit comments

Comments
 (0)