| Severity | Error |
| Reported by | Metalama.Framework |
| Message | The null-conditional operator cannot be used in the expression '<Expression>' because '<CompileTimeExpression>' is compile-time and '.<RunTimeMember>' returns a run-time value. When the compile-time expression is null, its type is unknown, so type-preserving run-time code cannot be generated. Use a compile-time null check (e.g. an 'if' statement) instead of the '?.' operator. |
Cause
Some members of the code model are evaluated at compile time but return a run-time expression. The most common one is the Value property of a parameter: the parameter is known at compile time, but its value exists only when the program runs. When a template accesses such a member, Metalama emits run-time code that has the type of the expression.
If the object on which you access the member is a compile-time expression that can be null, the null-conditional operator ?. can't be translated. When the compile-time expression is null, there's no declaration to take the type from, so Metalama can't generate the run-time code of the null branch. Metalama reports LAMA0289 when the left side of ?. is a compile-time expression and the member right after ?. returns a run-time value. It doesn't matter whether another member access, an invocation, or an element access follows that member: x?.Value.ToLower() and x?.Value[0] are reported like x?.Value. In the message, <Expression> is the whole expression, <CompileTimeExpression> is its compile-time part, and <RunTimeMember> is the member.
In the following example, the template looks for a parameter named token with FirstOrDefault, which returns null when there's no such parameter, and accesses its Value with ?..
1using Metalama.Framework.Aspects;
2using System.Linq;
3
4namespace Doc.LAMA0289.Error;
5
6public class LogTokenAttribute : OverrideMethodAspect
7{
8 public override dynamic? OverrideMethod()
9 {
10 // Error: '?.' is applied to a compile-time parameter to access its run-time value.
Error LAMA0289: The null-conditional operator cannot be used in the expression 'meta.Target.Parameters.FirstOrDefault( p => p.Name == "token" )?.Value?.ToLower()' because 'meta.Target.Parameters.FirstOrDefault( p => p.Name == "token" )' is compile-time and '.Value' returns a run-time value. When the compile-time expression is null, its type is unknown, so type-preserving run-time code cannot be generated. Use a compile-time null check (e.g. an 'if' statement) instead of the '?.' operator.
11 var normalizedToken = meta.Target.Parameters.FirstOrDefault( p => p.Name == "token" )?.Value?.ToLower();
12
13 return meta.Proceed();
14 }
15}
16
1using System;
2
3namespace Doc.LAMA0289.Error;
4
5internal class SessionService
6{
7 [LogToken]
8 public void Open( string token ) => Console.WriteLine( "Session opened." );
9}
10
Resolution
Replace the ?. operator with a compile-time null check. Store the compile-time expression in a local variable and test it with an if statement. Because the condition is compile-time, Metalama evaluates it while it expands the template and emits the code of the if block only when the declaration exists. Inside the block, access the run-time member with the ordinary . operator. You can still use ?. on the run-time value itself, as in the following example.
1using Metalama.Framework.Aspects;
2using System;
3using System.Linq;
4
5namespace Doc.LAMA0289.Fixed;
6
7public class LogTokenAttribute : OverrideMethodAspect
8{
9 public override dynamic? OverrideMethod()
10 {
11 var tokenParameter = meta.Target.Parameters.FirstOrDefault( p => p.Name == "token" );
12
13 // Fixed: a compile-time 'if' statement checks for null instead of the '?.' operator.
14 if ( tokenParameter != null )
15 {
16 var normalizedToken = tokenParameter.Value?.ToLower();
17 Console.WriteLine( $"Normalized token: {normalizedToken}" );
18 }
19
20 return meta.Proceed();
21 }
22}
23
1using System;
2
3namespace Doc.LAMA0289.Fixed;
4
5internal class SessionService
6{
7 [LogToken]
8 public void Open( string token ) => Console.WriteLine( "Session opened." );
9}
10
1using System;
2
3namespace Doc.LAMA0289.Fixed;
4
5internal class SessionService
6{
7 [LogToken]
8 public void Open(string token)
9 {
10 var normalizedToken = token?.ToLower();
11 Console.WriteLine($"Normalized token: {normalizedToken}");
12 Console.WriteLine("Session opened.");
13 }
14}
15