| Severity | Error |
| Reported by | Metalama.Framework |
| Message | Cannot apply the aspect '<Aspect>' to '<Target>': the template returns '<Expression>', of type '<Type>', from a method or local function whose return type is '<ReturnType>'. |
Cause
In a template, the type of meta.Proceed() is the return type of the target method, which Metalama only knows when it applies the aspect. When the target method returns void, meta.Proceed() has no value.
Metalama reports LAMA0247 when it expands a template that returns such a void expression from a context that expects a value. The typical case is a return meta.Proceed(); statement in a local function that has a non-void return type, like bool, when the aspect is applied to a void method. Because local functions in templates can't use dynamic in their signature (see LAMA0246), their return type is fixed, while the target method can return anything.
The message gives the returned expression, its type, and the return type of the local function or method. Metalama reports the error on the target declaration.
In the following example, the RetryOnFailure aspect wraps meta.Proceed() in a local function that returns bool, so it can call the method a second time when the first call fails. The aspect is also applied to a method that returns void.
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.LAMA0247.Error;
5
6public class RetryOnFailureAttribute : OverrideMethodAspect
7{
8 public override dynamic? OverrideMethod()
9 {
10 // The local function returns bool, so the target method must return bool.
11 bool Execute()
12 {
13 return meta.Proceed();
14 }
15
16 var succeeded = Execute();
17
18 if ( !succeeded )
19 {
20 Console.WriteLine( "Retrying once." );
21 succeeded = Execute();
22 }
23
24 return succeeded;
25 }
26}
27
1using System;
2using System.IO;
3
4namespace Doc.LAMA0247.Error;
5
6internal class FileStore
7{
8 [RetryOnFailure]
9 public bool TryDelete( string path )
10 {
11 if ( !File.Exists( path ) )
12 {
13 return false;
14 }
15
16 File.Delete( path );
17
18 return true;
19 }
20
21 // Error: the method returns void, so the aspect cannot return its result from a bool local function.
22 [RetryOnFailure]
Error LAMA0247: Cannot apply the aspect 'RetryOnFailure' to 'FileStore.Clear()': the template returns 'meta.Proceed()', of type 'void', from a method or local function whose return type is 'bool'.
23 public void Clear() => Console.WriteLine( "Clearing the store." );
24}
25
Resolution
Make sure that the template returns void expressions only where no value is expected:
- Apply the aspect only to methods whose return type matches the local function, as in the following example. If you're the author of the aspect, add an eligibility condition on the return type in BuildEligibility, so that the aspect can't be applied to other methods.
- If the aspect must support
voidmethods, handle them in a separate branch of the template, for instance with a compile-timeifonmeta.Target.Method.ReturnType, where meta.Proceed() is called as a statement.
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.LAMA0247.Fixed;
5
6public class RetryOnFailureAttribute : OverrideMethodAspect
7{
8 public override dynamic? OverrideMethod()
9 {
10 // The local function returns bool, so the target method must return bool.
11 bool Execute()
12 {
13 return meta.Proceed();
14 }
15
16 var succeeded = Execute();
17
18 if ( !succeeded )
19 {
20 Console.WriteLine( "Retrying once." );
21 succeeded = Execute();
22 }
23
24 return succeeded;
25 }
26}
27
1using System;
2using System.IO;
3
4namespace Doc.LAMA0247.Fixed;
5
6internal class FileStore
7{
8 [RetryOnFailure]
9 public bool TryDelete( string path )
10 {
11 if ( !File.Exists( path ) )
12 {
13 return false;
14 }
15
16 File.Delete( path );
17
18 return true;
19 }
20
21 // Fixed: the aspect is applied only to methods that return bool.
22 public void Clear() => Console.WriteLine( "Clearing the store." );
23}
24
1using System;
2using System.IO;
3
4namespace Doc.LAMA0247.Fixed;
5
6internal class FileStore
7{
8 [RetryOnFailure]
9 public bool TryDelete(string path)
10 {
11 bool Execute()
12 {
13 if (!File.Exists(path))
14 {
15 return false;
16 }
17
18 File.Delete(path);
19
20 return true;
21 }
22
23 var succeeded = Execute();
24 if (!succeeded)
25 {
26 Console.WriteLine("Retrying once.");
27 succeeded = Execute();
28 }
29
30 return succeeded;
31 }
32 // Fixed: the aspect is applied only to methods that return bool.
33 public void Clear() => Console.WriteLine("Clearing the store.");
34}
35