feO2x
feO2x

Reputation: 5728

How do I properly specify the SyntaxNodes for nameof to Roslyn when using the CSharp.SyntaxFactory?

I'm currently writing a code generator that incorporates Roslyn, but am stuck on how to properly create the C# syntax nodes for the nameofoperator. As an MCVE, I want to build the Syntax Tree for the following code and then compile it:

using System;

namespace CompilationTests
{
    public class Foo
    {
        public const string Bar = nameof(Array.Length);
    }
}

The using, namespace, and class declarations are no problem, but I can't get the invocation expression right. According to Syntax Visualizer and Roslyn Quoter, nameof is simply an IdentifierName("nameof") expression, and Array.Length is a SimpleMemberAccessExpression:

Syntax Node Structure according to Syntax Visualizer

I then tried to build up the syntax nodes for the field with the following code:

FieldDeclaration(
    VariableDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)))
        .WithVariables(
            SingletonSeparatedList(
                VariableDeclarator(Identifier("Bar"))
                    .WithInitializer(
                        EqualsValueClause(
                            InvocationExpression(IdentifierName("nameof"))
                                .WithArgumentList(
                                    ArgumentList(
                                        SingletonSeparatedList(
                                            Argument(
                                                MemberAccessExpression(
                                                    SyntaxKind.SimpleMemberAccessExpression,
                                                    IdentifierName("Array"),
                                                    IdentifierName("Length")))))))))))
.WithModifiers(
    TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)))

However, when I create a compilation and emit it to a stream, it fails to resolve nameofand Aray.Length, giving the following compiler diagnostics:

(8,35): error CS0103: The name 'nameof' does not exist in the current context
(8,42): error CS0120: An object reference is required for the non-static field, method, or property 'Array.Length'

But when I simply pass the source code to CSharpSyntaxTree.Parse as a string instead of creating the syntax tree on my own, everything compiiles just fine.

My question is : how can I create the syntax nodes for nameof(Array.Length) properly? Or am I missing some options when creating the syntax tree?

For the sake of completeness, here is the full MCVE, simply paste it into an xunit test project that also references Microsoft.CodeAnalysis.CSharp (as of this writing, 3.6.0).

using System;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Xunit;
using Xunit.Abstractions;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace CompilationTests
{
    public sealed class CSharpCompilationTests
    {
        private readonly ITestOutputHelper _output;

        public CSharpCompilationTests(ITestOutputHelper output) => _output = output;

        [Fact]
        public void Compile()
        {
            var rootNode =
                CompilationUnit()
                   .WithUsings(SingletonList(UsingDirective(IdentifierName("System"))))
                   .WithMembers(
                        SingletonList<MemberDeclarationSyntax>(
                            NamespaceDeclaration(IdentifierName("CompilationTests"))
                               .WithMembers(
                                    SingletonList<MemberDeclarationSyntax>(
                                        ClassDeclaration("Foo")
                                           .WithMembers(
                                                SingletonList<MemberDeclarationSyntax>(
                                                    FieldDeclaration(
                                                            VariableDeclaration(PredefinedType(Token(SyntaxKind.StringKeyword)))
                                                               .WithVariables(
                                                                    SingletonSeparatedList(
                                                                        VariableDeclarator(Identifier("Bar"))
                                                                           .WithInitializer(
                                                                                EqualsValueClause(
                                                                                    InvocationExpression(IdentifierName("nameof"))
                                                                                       .WithArgumentList(
                                                                                            ArgumentList(
                                                                                                SingletonSeparatedList(
                                                                                                    Argument(
                                                                                                        MemberAccessExpression(
                                                                                                            SyntaxKind.SimpleMemberAccessExpression,
                                                                                                            IdentifierName("Array"),
                                                                                                            IdentifierName("Length")))))))))))
                                                       .WithModifiers(
                                                            TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)))))))))
                   .NormalizeWhitespace();

            var compilation = CSharpCompilation.Create(
                "MyAssembly",
                new[] { CSharpSyntaxTree.Create(rootNode, CSharpParseOptions.Default, isGeneratedCode: true) },
                new[] { MetadataReference.CreateFromFile(typeof(Array).Assembly.Location) },
                new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
            );

            using var memoryStream = new MemoryStream();
            var result = compilation.Emit(memoryStream);

            if (!result.Diagnostics.IsEmpty)
            {
                foreach (var diagnostic in result.Diagnostics)
                    _output.WriteLine(diagnostic.ToString());
                _output.WriteLine(string.Empty);
            }

            _output.WriteLine(rootNode.ToString());

            Assert.True(result.Success);
        }
    }

    public class Foo
    {
        public const string Bar = nameof(Array.Length);
    }
}

Thank you so much for your help in advance!

Upvotes: 1

Views: 821

Answers (2)

Jeff Mercado
Jeff Mercado

Reputation: 134581

If you're ever unsure how to express a given construct, you could always use the SyntaxFactory.Parse*() methods to see how it would be parsed.

In this case, using SyntaxFactory.ParseExpression("nameof(Array.Length)"), it's an InvocationExpression (as it shows in your screenshot). Remember, nameof is a contextual keyword, it's not a reserved keyword. So its treated like any usable identifier, but has special meaning in certain contexts. In this case, when used like a method invocation is interpreted as what we know as the nameof operator.

So to generate this expression using the syntax factory by hand, it would be this:

SyntaxFactory.InvocationExpression(
    SyntaxFactory.IdentifierName("nameof")
)
.WithArgumentList(
    SyntaxFactory.ArgumentList(
        SyntaxFactory.SingletonSeparatedList(
            SyntaxFactory.Argument(
                SyntaxFactory.MemberAccessExpression(
                    SyntaxKind.SimpleMemberAccessExpression,
                    SyntaxFactory.IdentifierName("Array"),
                    SyntaxFactory.IdentifierName("Length")
                )
            )
        )
    )
)

Upvotes: 1

LaniusExcubitor
LaniusExcubitor

Reputation: 356

It has been 8 month, but for anyone searching for an answer: The nameof identifier is not a normal identifier. Instead of

IdentifierName("nameof")

you need

IdentifierName(Identifier(TriviaList(), 
                          SyntaxKind.NameOfKeyword,
                          "nameof",
                          "nameof",
                          TriviaList()));

Upvotes: 1

Related Questions