| Severity | Error |
| Reported by | Metalama.Framework |
| Message | Cannot instantiate a custom attribute: cannot find the compile-time type '<Type>'. Make sure that the type exists and is annotated with [CompileTime] or [RunTimeOrCompileTime]. |
Cause
Compile-time code, such as an aspect or a fabric, can read the custom attributes of a declaration through the code model. Usually, it reads their constructor and named arguments. It can also get an instance of the attribute class with the TryConstruct or Construct methods. To create this instance, Metalama must load the attribute class at compile time, which is only possible when the class is part of the compile-time code of the project or of its references.
Metalama reports LAMA0401 when it must instantiate an attribute, but the attribute type <Type> isn't available at compile time. Typically, the attribute class is a plain run-time class: it isn't annotated with [RunTimeOrCompileTime] or [CompileTime]. The error is reported on the custom attribute.
In the following example, the Log aspect calls TryConstruct to read the category given by a [LogCategory] attribute, but LogCategoryAttribute is a run-time-only class.
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System;
4using System.Linq;
5
6namespace Doc.LAMA0401.Error;
7
8public class LogAttribute : MethodAspect
9{
10 public override void BuildAspect( IAspectBuilder<IMethod> builder )
11 {
12 var category = "Default";
13
14 var categoryAttribute = builder.Target.Attributes.FirstOrDefault( a => a.Type.Name == "LogCategoryAttribute" );
15
16 // Instantiates the [LogCategory] attribute at compile time.
17 if ( categoryAttribute != null && categoryAttribute.TryConstruct( builder.Diagnostics, out var instance ) )
18 {
19 category = instance.ToString() ?? category;
20 }
21
22 builder.Override( nameof(this.OverrideMethod), args: new { category } );
23 }
24
25 [Template]
26 public dynamic? OverrideMethod( [CompileTime] string category )
27 {
28 Console.WriteLine( $"[{category}] Executing {meta.Target.Method}." );
29
30 return meta.Proceed();
31 }
32}
33
1using System;
2
3namespace Doc.LAMA0401.Error;
4
5// Error: the attribute is run-time-only, so the [Log] aspect can't instantiate it at compile time.
6[AttributeUsage( AttributeTargets.Method )]
7public class LogCategoryAttribute : Attribute
8{
9 public LogCategoryAttribute( string category )
10 {
11 this.Category = category;
12 }
13
14 public string Category { get; }
15
16 public override string ToString() => this.Category;
17}
18
19internal class InvoiceService
20{
21 [Log]
Error LAMA0401: Cannot instantiate a custom attribute: cannot find the compile-time type 'LogCategoryAttribute'. Make sure that the type exists and is annotated with [CompileTime] or [RunTimeOrCompileTime].
22 [LogCategory( "Billing" )]
23 public void SendInvoice( int invoiceId )
24 {
25 Console.WriteLine( $"Sending invoice {invoiceId}." );
26 }
27}
28
Resolution
Depending on your situation, do one of the following:
- Annotate the attribute class with [RunTimeOrCompileTime], so that it's available both at run time and at compile time. The project that declares the attribute then needs a reference to the
Metalama.Frameworkpackage. The following example shows this fix. - If you can't change the attribute class, for instance because it comes from a package you don't control, don't instantiate the attribute. Read its arguments with the ConstructorArguments and NamedArguments properties instead.
1using Metalama.Framework.Aspects;
2using Metalama.Framework.Code;
3using System;
4using System.Linq;
5
6namespace Doc.LAMA0401.Fixed;
7
8public class LogAttribute : MethodAspect
9{
10 public override void BuildAspect( IAspectBuilder<IMethod> builder )
11 {
12 var category = "Default";
13
14 var categoryAttribute = builder.Target.Attributes.FirstOrDefault( a => a.Type.Name == "LogCategoryAttribute" );
15
16 // Instantiates the [LogCategory] attribute at compile time.
17 if ( categoryAttribute != null && categoryAttribute.TryConstruct( builder.Diagnostics, out var instance ) )
18 {
19 category = instance.ToString() ?? category;
20 }
21
22 builder.Override( nameof(this.OverrideMethod), args: new { category } );
23 }
24
25 [Template]
26 public dynamic? OverrideMethod( [CompileTime] string category )
27 {
28 Console.WriteLine( $"[{category}] Executing {meta.Target.Method}." );
29
30 return meta.Proceed();
31 }
32}
33
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.LAMA0401.Fixed;
5
6// Fixed: the attribute is available at compile time, so the [Log] aspect can instantiate it.
7[AttributeUsage( AttributeTargets.Method )]
8[RunTimeOrCompileTime]
9public class LogCategoryAttribute : Attribute
10{
11 public LogCategoryAttribute( string category )
12 {
13 this.Category = category;
14 }
15
16 public string Category { get; }
17
18 public override string ToString() => this.Category;
19}
20
21internal class InvoiceService
22{
23 [Log]
24 [LogCategory( "Billing" )]
25 public void SendInvoice( int invoiceId )
26 {
27 Console.WriteLine( $"Sending invoice {invoiceId}." );
28 }
29}
30
1using Metalama.Framework.Aspects;
2using System;
3
4namespace Doc.LAMA0401.Fixed;
5
6// Fixed: the attribute is available at compile time, so the [Log] aspect can instantiate it.
7[AttributeUsage(AttributeTargets.Method)]
8[RunTimeOrCompileTime]
9public class LogCategoryAttribute : Attribute
10{
11 public LogCategoryAttribute(string category)
12 {
13 this.Category = category;
14 }
15
16 public string Category { get; }
17
18 public override string ToString() => this.Category;
19}
20
21internal class InvoiceService
22{
23 [Log]
24 [LogCategory("Billing")]
25 public void SendInvoice(int invoiceId)
26 {
27 Console.WriteLine("[Billing] Executing InvoiceService.SendInvoice(int).");
28 Console.WriteLine($"Sending invoice {invoiceId}.");
29 }
30}
31