SourceGenerator: Attribute ConstructorArguments is empty
Asked Answered
W

2

6

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();
}

See how the ConstructorArguments property is empty

Have I misunderstood this API? What am I doing wrong?

Whitsunday answered 5/11, 2021 at 12:28 Comment(0)
L
5

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.

Ludlow answered 11/11, 2021 at 14:32 Comment(1)
Thanks @Youssef13; it never really occurred to me that by the time I was adding my source files the compilation had already been done... The update you gave for v3.9 of Microsoft.CodeAnalysis is indeed much betterWhitsunday
A
4

The AttributeClass property is an ErrorType, which means that the compiler didn't actually know what that type was. We fill in a fake "error" type when we had a name for the type but otherwise didn't know what it was; this makes some downstream handling easier for certain features. In any case, figure out where that's coming from. You commented that "This compiles without errors or warnings" but whatever environment your Roslyn code is running in, you likely have a compilation that will give errors.

Allowable answered 5/11, 2021 at 17:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.