This article helps you understand why a call is not intercepted, and lists the limitations of this version.
Seeing what was intercepted
The editor shows the source code, not the rewritten calls, and the generated interceptors are not visible in the code model of aspects. To see which calls were rewritten, and the code of the generated interceptors, look at the transformed code. See Debugging aspects.
Calls that are never intercepted
Only the calls written in the source code of the current project are intercepted. These calls are never intercepted:
- Calls that the compiler, the runtime or a library makes on behalf of the code, because there is no call at the call site. For instance, an interpolated string or
Console.WriteLine( object )callsToStringinside the runtime, aforeachstatement callsGetEnumerator, ausingstatement callsDispose, and collections callEqualsandGetHashCode. - Calls in code that aspects introduce.
- Calls in the output of Roslyn source generators, because source generators run after Metalama.
- Calls inside other assemblies, including the intercepted library itself.
- Calls through reflection,
dynamic, or a delegate. - Method groups in a
nameofexpression.
Calls outside the scope
A call is intercepted only when it is in the scope of a registration. Check that:
- the call is in the target of the aspect, or in the declarations selected by the fabric;
- the call is not in a nested type, unless the registration calls
IncludingNestedTypes(); - the call is not in a lambda or a local function when the registration calls
ExcludingLambdas(); - the call is not in a declaration that has the
[ExcludeInterceptors]attribute; - the called method is selected. A method that a type inherits is not selected by a selection of the derived type: select the base type. A call bound to an interface method is not a call to its implementation: select the interface, or use
IncludingInterfaceImplementations().
See Choosing which calls to intercept.
Calls that cannot be intercepted
Some calls match a registration but cannot be intercepted in this version. When the registration returns an interceptor for such a call, Metalama reports the warning LAMA1012 and leaves the call unchanged. In ForEachSite, read NonInterceptableReason and skip these call sites to avoid the warning.
For instance:
16.ForEachSite(
17 site => site.NonInterceptableReason != NonInterceptableReason.None
18 ? site.Skip()
19 : site.RedirectToSynthesizedMethod( nameof(this.Trace) ) ) );| Reason | Description |
|---|---|
RefReturn |
The intercepted method returns by reference. |
ScopedParameter |
The signature requires scoped or [UnscopedRef]. |
PointerType |
A pointer or function pointer type is in the signature, in a type argument, or in the type of the receiver. |
VariableArguments |
The intercepted method uses __arglist. |
UnsupportedDefaultValue |
An omitted optional argument has a default value that cannot be written in C#, and the interceptor receives it. |
ConditionalAccessMutableReceiver |
The call is in a conditional access, and the receiver is a type parameter that would have to be passed by reference. |
PromotedFieldInitializer |
The call is in the initializer of a field that an aspect promotes to a property. |
MethodReferenceReceiverNotSupported |
The receiver of a method group can be kept neither in a method group nor in a wrapper. |
ExpressionTree |
The call is in a lambda converted to an expression tree, for instance in a query on IQueryable. The compiler describes the call instead of making it. |
Later versions can add values to this enumeration.
Example: Calls that cannot be intercepted
In the following example, the call to a method that returns by reference, and the call in a lambda converted to an expression tree, cannot be intercepted. Metalama reports LAMA1012 for each of them. The same call in a lambda converted to a delegate is intercepted.
1using Metalama.Extensions.Interceptors;
2using Metalama.Framework.Aspects;
3using Metalama.Framework.Code;
4using System;
5
6namespace Doc.UnsupportedCalls;
7
8public class TraceCallsAttribute : TypeAspect
9{
10 public override void BuildAspect( IAspectBuilder<INamedType> builder )
11 => builder.InterceptMethods(
12 calls => calls
13 .Type( typeof(Store) )
14 .AllMethods()
15 .RedirectToSynthesizedMethod( nameof(this.Trace) ) );
16
17 [Template]
18 public dynamic? Trace()
19 {
20 Console.WriteLine( $"Calling {meta.Interception.Method.InterceptedMethod.Name}." );
21
22 return meta.Proceed();
23 }
24}
25
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5namespace Doc.UnsupportedCalls;
6
7[TraceCalls]
8public class Inventory
9{
Warning LAMA1012: The call to 'Store.Slot()' cannot be intercepted by the interceptor template 'Trace' registered by the aspect [TraceCallsAttribute] applied to 'Inventory' because the method returns by reference. The call site is left unchanged.
10 public void Reset() => Store.Slot() = 0;
11
12 public IQueryable<int> FindLarge( IQueryable<int> quantities )
Warning LAMA1012: The call to 'Store.IsLarge(int)' cannot be intercepted by the interceptor template 'Trace' registered by the aspect [TraceCallsAttribute] applied to 'Inventory' because it is in a lambda that is converted to an expression tree, which describes the call instead of making it. The call site is left unchanged.
13 => quantities.Where( q => Store.IsLarge( q ) );
14
15 public IEnumerable<int> FindLarge( IEnumerable<int> quantities )
16 => quantities.Where( q => Store.IsLarge( q ) );
17}
18
19public static class Store
20{
21 private static int _slot;
22
23 // A method that returns by reference cannot be intercepted.
24 public static ref int Slot() => ref _slot;
25
26 public static bool IsLarge( int quantity ) => quantity > 100;
27}
28
1using System;
2using System.Collections.Generic;
3using System.Linq;
4
5namespace Doc.UnsupportedCalls;
6
7[TraceCalls]
8public class Inventory
9{
10 public void Reset() => Store.Slot() = 0;
11
12 public IQueryable<int> FindLarge(IQueryable<int> quantities)
13 => quantities.Where(q => Store.IsLarge(q));
14
15 public IEnumerable<int> FindLarge(IEnumerable<int> quantities)
16 => quantities.Where(q => MetalamaInterceptors.Store_IsLarge_Interceptor(quantity: q));
17}
18
19public static class Store
20{
21 private static int _slot;
22
23 // A method that returns by reference cannot be intercepted.
24 public static ref int Slot() => ref _slot;
25
26 public static bool IsLarge(int quantity) => quantity > 100;
27}
28
using System;
using Doc.UnsupportedCalls;
internal static class MetalamaInterceptors
{
internal static bool Store_IsLarge_Interceptor(int quantity)
{
Console.WriteLine("Calling IsLarge.");
return Store.IsLarge(quantity);
}
}Uses that are not supported in this version
- Property and indexer accesses, event accessors, constructor calls, operators and
awaitexpressions cannot be intercepted. - C# 14 extension members cannot be intercepted.
- Interceptors generated from templates intercept only calls, not method groups.
- An existing method cannot receive a receiver whose type is a type parameter, and cannot intercept a
basecall to a virtual method.
Diagnostics
| Diagnostic | Severity | Cause |
|---|---|---|
| LAMA1012 | Warning | The call cannot be intercepted in this version. The reason is part of the message. The call is left unchanged. |
| LAMA1013 | Error | The parameters of an existing method cannot be bound to the call. The message gives the parameter and the reason. |
| LAMA1014 | Error | The interceptor or its Configure delegate is invalid for the call, for instance a template for a method group, or an added parameter without a value. |
| LAMA1016 | Error | The template cannot be found or cannot implement the interceptor. |
| LAMA1024 | Error | The location of the generated interceptor cannot be used for the call, for instance PlaceInCallingType in top-level statements. |
| LAMA0661 | Warning | A variable that hides a parameter of the calling member passed to the interceptor was renamed. |
Other tools that rewrite calls
- Metalama interceptors and the C#
[InterceptsLocation]interceptors that source generators produce are independent mechanisms. Do not intercept the same call with both. - Aspects that run after a low-level aspect weaver cannot register interceptors.