Open sandboxFocusImprove this doc

LAMA0226: A construct can't combine run-time and compile-time elements

Severity Error
Reported by Metalama.Framework
Message '<Symbol>' is invalid because '<RunTimeSymbol>' is run-time but '<CompileTimeSymbol>' is compile-time.

Cause

Metalama assigns an execution scope to every type and member. Compile-time types, like the code model types IMethod or IParameter, exist only while Metalama compiles the project. Run-time types, like the classes of your project, exist only in the compiled program. Some types, like string or Dictionary<TKey, TValue>, are available in both scopes, and the scope of a generic type instance depends on its type arguments.

Metalama reports LAMA0226 when a construct depends on both a run-time-only element and a compile-time-only element, so that it can exist in neither scope. The most common cases are:

  • a generic type or method instance whose type arguments include a run-time-only type and a compile-time-only type, for instance Dictionary<IParameter, Customer>, or a call to meta.CompileTime with a run-time type argument;
  • a type that has both a run-time-only and a compile-time-only base type or interface.

The first placeholder of the message is the invalid construct, the second one is the run-time element, and the third one is the compile-time element.

In the following example, the template declares a dictionary that maps each compile-time parameter, IParameter, to a run-time AuditEntry object.

1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System.Collections.Generic;
4
5namespace Doc.LAMA0226.Error;
6
7public class AuditAttribute : OverrideMethodAspect
8{
9    public override dynamic? OverrideMethod()
10    {
11        // Error: the type combines the compile-time IParameter and the run-time AuditEntry.
        Error LAMA0226: 'Dictionary<IParameter, AuditEntry>' is invalid because 'AuditEntry' is run-time but 'IParameter' is compile-time.

12        Dictionary<IParameter, AuditEntry> entries = new();
13
14        return meta.Proceed();
15    }
16}
17
1using System;
2using System.Collections.Generic;
3
4namespace Doc.LAMA0226.Error;
5
6public class AuditEntry
7{
8    public AuditEntry( object? value )
9    {
10        this.Value = value;
11    }
12
13    public object? Value { get; }
14}
15
16public static class AuditLog
17{
18    public static void Write( string methodName, Dictionary<string, AuditEntry> entries )
19        => Console.WriteLine( $"{methodName} called with {entries.Count} argument(s)." );
20}
21
22internal class OrderService
23{
24    [Audit]
25    public void PlaceOrder( string product, int quantity ) { }
26}
27

Resolution

Decide whether the construct is compile-time or run-time, and replace the elements of the other scope:

  • For a run-time construct, replace the compile-time elements with run-time values. For instance, use the name of the parameter, a string, instead of the IParameter, as shown in the following example.
  • For a compile-time construct, replace the run-time types with compile-time representations, for instance an IType or IExpression instead of a run-time type.
  • For a type with conflicting base types, split it into a run-time type and a compile-time type.

1using Metalama.Framework.Aspects;
2using System.Collections.Generic;
3
4namespace Doc.LAMA0226.Fixed;
5
6public class AuditAttribute : OverrideMethodAspect
7{
8    public override dynamic? OverrideMethod()
9    {
10        // Fixed: the dictionary is keyed by the parameter name, so all its type arguments are run-time.
11        var entries = new Dictionary<string, AuditEntry>();
12
13        foreach ( var parameter in meta.Target.Parameters )
14        {
15            entries.Add( parameter.Name, new AuditEntry( parameter.Value ) );
16        }
17
18        AuditLog.Write( meta.Target.Method.Name, entries );
19
20        return meta.Proceed();
21    }
22}
23
Source Code
1using System;
2using System.Collections.Generic;
3
4namespace Doc.LAMA0226.Fixed;
5
6public class AuditEntry
7{
8    public AuditEntry( object? value )
9    {
10        this.Value = value;
11    }
12
13    public object? Value { get; }
14}
15
16public static class AuditLog
17{
18    public static void Write( string methodName, Dictionary<string, AuditEntry> entries )
19        => Console.WriteLine( $"{methodName} called with {entries.Count} argument(s)." );
20}
21
22internal class OrderService
23{
24    [Audit]
25    public void PlaceOrder( string product, int quantity ) { }
26}
27
Transformed Code
1using System;
2using System.Collections.Generic;
3
4namespace Doc.LAMA0226.Fixed;
5
6public class AuditEntry
7{
8    public AuditEntry(object? value)
9    {
10        this.Value = value;
11    }
12
13    public object? Value { get; }
14}
15
16public static class AuditLog
17{
18    public static void Write(string methodName, Dictionary<string, AuditEntry> entries)
19        => Console.WriteLine($"{methodName} called with {entries.Count} argument(s).");
20}
21
22internal class OrderService
23{
24    [Audit]
25    public void PlaceOrder(string product, int quantity)
26    {
27        var entries = new Dictionary<string, AuditEntry>();
28        entries.Add("product", new AuditEntry(product));
29        entries.Add("quantity", new AuditEntry(quantity));
30        AuditLog.Write("PlaceOrder", entries);
31    }
32}
33

See also