0

I'm writing a Roslyn source generator, where I need to grab the initializer from the VariableDeclaratorSyntax.Initializer of a field, and echo it back into a generated file.

For example, if I wrote this code....

using MyNamespace.SomeStuff;
...
partial class Bar
{
    [Convert]
    Foo _fooField = Foo.Baz;
}

My source generator might produce...

partial class Bar
{
    MyNamespace.SomeStuff.Foo _fooField = MyNamespace.SomeStuff.Foo.Baz;
}

I can get the fully-qualified field Type easily, via IFieldSymbol. However, I don't know how to get a fully-qualified version of the initializer.

This value can be anything. It need not be a static; it could be nested constructors for custom classes; etc. I need to fully-qualify all of the symbols, and print out the result as a string that I can insert into my generated code without bothering with usings.

I think I need to make a custom CSharpSyntaxRewriter that visits the proper nodes and replaces the names with a resolved version from the SemanticModel. But there isn't much information on the internet on how to do this.

Here's what I've got so far:

public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
{
    var symbol = SemanticModel.GetSymbolInfo(node);
    if (symbol.Symbol is not null)
        return node.WithName(SyntaxFactory.IdentifierName(symbol.Symbol.ToString()));
    else return base.VisitMemberAccessExpression(node);
}

But instead of MyNamespace.SomeStuff.Foo.Baz it produces Foo.MyNamespace.SomeStuff.Foo.Baz which obviously doesn't work. I need to use WithExpression instead of WithName, but the expression could be anything and I don't know how to narrow it down.

Any ideas? Is there a better way of doing this?

EDIT: As requested, here's the full context:

[Generator]
public class StyleSourceGenerator : IIncrementalGenerator
{
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // find any field tagged with [Convert]
        var fields = context.SyntaxProvider.ForAttributeWithMetadataName(typeof(ConvertAttribute).FullName,
            (node, token) =>
            {
                return node is VariableDeclaratorSyntax;
            },
            (ctx, token) =>
            {
                var syntax = (VariableDeclaratorSyntax)ctx.TargetNode;
                syntax.Initializer.Value.ToString(); // this will print Foo.Baz, when I need it to print MyNamespace.SomeStuff.Foo.Baz
            });
    }
}


2
  • In var symbol = SemanticModel.GetSymbolInfo(node); where does SemanticModel come from? More generally, can you provide a MRE of your source generator and syntax rewriter? Commented Oct 13, 2023 at 13:35
  • I provided some more context in the answer, and added a solution that works for me so far. I'd appreciate another set of eyes though since I don't think it's particularly robust. Commented Oct 14, 2023 at 23:03

1 Answer 1

0

I've found a solution that works for my case, but I don't know if it's fully bulletproof. There's likely some things (like constructors, maybe?) that wouldn't hold up, but this works for member access.

I'd appreciate advice/edits on how to make it more robust.

From my original example, get the corrected initializer with:

var newInitializer = new (QualifiedWriter(ctx.SemanticModel))
    .Visit(syntax.Initializer) as EqualsValueClauseSyntax;
internal class QualifiedWriter : CSharpSyntaxRewriter
{
    public QualifiedWriter(SemanticModel semanticModel)
    {
        SemanticModel = semanticModel;
    }

    private SemanticModel SemanticModel { get; }

    public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
    {
        // for example: Microsoft.Xna.Color.Transparent;  identifier name is Transparent, expression is Microsoft.Xna.Color
        // Then we have a russian nesting doll of SimpleMemberAccessExpression until the expression is just an IdentifierNameSyntax

        return ResolveMemberAccessSyntaxTree(node);
    }

    public MemberAccessExpressionSyntax ResolveMemberAccessSyntaxTree(MemberAccessExpressionSyntax node)
    {
        if (node.Expression is MemberAccessExpressionSyntax access) return node.WithExpression(ResolveMemberAccessSyntaxTree(access));

        if (node.Expression is IdentifierNameSyntax name)
        {
            var symbol = SemanticModel.GetSymbolInfo(name);
            if (symbol.Symbol is null) return node; // give up
            return node.WithExpression(name.WithIdentifier(SyntaxFactory.Identifier(symbol.Symbol.ToString())));
        }

        // we don't know how to nest further!
        return node;
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.