Reputation: 5728
I'm currently writing a code generator that incorporates Roslyn, but am stuck on how to properly create the C# syntax nodes for the nameof
operator. 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
:
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 nameof
and 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
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
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