I am writing a source generator but am struggling to get the value of an argument passed to the constructor of my attribute.
I inject the following into the compilation:
namespace Richiban.Cmdr
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CmdrMethod : System.Attribute
{
private readonly string _alias;
public CmdrMethod(string alias)
{
_alias = alias;
}
}
}
And then in my sample application I have the following:
public static class InnerContainerClass
{
[CmdrMethod("test")]
public static void AnotherMethod(Data data)
{
Console.WriteLine($"In {nameof(AnotherMethod)}, {new { data }}");
}
}
This compiles without errors or warnings and I am successfully able to find all methods that have been decorated with my CmdrMethod
attribute, but I am unable to get the value passed to the attribute because, for some reason, the ConstructorArguments
property of the attribute is empty:
private static ImmutableArray<string> GetAttributeArguments(
IMethodSymbol methodSymbol,
string attributeName)
{
var attr = methodSymbol
.GetAttributes()
.Single(a => a.AttributeClass?.Name == attributeName);
var arguments = attr.ConstructorArguments;
if (methodSymbol.Name == "AnotherMethod")
Debugger.Launch();
return arguments.Select(a => a.ToString()).ToImmutableArray();
}
Have I misunderstood this API? What am I doing wrong?
The Compilation
is immutable. When you add the source code for your attribute, you still have a Compilation
object that don't know anything about the attribute. This causes the AttributeClass
to be an ErrorType
as @jason-malinowski mentioned. This is very known for this kind of source generators, and the solution is just simple. Create a new Compilation
with the symbol you injected, then get a SemanticModel
from the new Compilation
:
// You should already have something similar to the following two lines.
SourceText attributeSourceText = SourceText.From("CmdrMethod source code here", Encoding.UTF8);
context.AddSource("CmdrMethod.g.cs" /* or whatever name you chose*/, attributeSourceText);
// This is the fix.
ParseOptions options = ((CSharpCompilation)context.Compilation).SyntaxTrees[0].Options;
SyntaxTree attributeTree = CSharpSyntaxTree.ParseText(atttributeSourceText, (CSharpParseOptions)options);
Compilation newCompilation = context.Compilation.AddSyntaxTrees(attributeTree);
// Get the semantic model from 'newCompilation'. It should have the information you need.
UPDATE: The experience became better starting from Microsoft.CodeAnalysis
3.9 packages, you can now add the attribute in Initialize
instead of Execute
:
public void Initialize(GeneratorInitializationContext context)
{
// Register the attribute source
context.RegisterForPostInitialization((i) => i.AddSource("CmdrMethod.g.cs", attributeText));
// .....
}
Then, you can directly work with the compilation you get in Execute
.
See AutoNotify sample.