2

[Edit] Solution: Problem were solved by below answers. I used this part of code:

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

Unfortunately there is no pattern as SyntaxFactory.TypeOfExpression.

Problem: I am writing a code generator (using Roslyn) which uses nameof. When I try to compile prepared SyntaxTree with Roslyn it fails with error "error CS0103: The name 'nameof' does not exist in the current context", whereas when I parse as SyntaxTree the full text of the same SyntaxTree compilation is done with no error.

That's my first post on StackOverflow, thank you in advance for your understanding.

I am writing a code generator (using Roslyn) which uses nameof. My compilation unit preparation stands as below:

private static CompilationUnitSyntax PrepareCompilationUnit(bool useNameof)
{
    ArrowExpressionClauseSyntax arrowExpression = useNameof
        ? SyntaxFactory.ArrowExpressionClause(
            SyntaxFactory.InvocationExpression(
                SyntaxFactory.IdentifierName(
                        SyntaxFactory.Token(SyntaxKind.NameOfKeyword).ToString()),
                    SyntaxFactory.ArgumentList(
                        SyntaxFactory.SeparatedList(
                            new[] { SyntaxFactory
                                        .Argument(SyntaxFactory
                                            .IdentifierName(PROPERTY_NAME)) }))))
        : SyntaxFactory.ArrowExpressionClause(
                SyntaxFactory.LiteralExpression(
                    SyntaxKind.StringLiteralExpression,
                    SyntaxFactory.Literal(PROPERTY_NAME)));

    PropertyDeclarationSyntax property = SyntaxFactory.PropertyDeclaration(
            SyntaxFactory.ParseTypeName("string"),
            SyntaxFactory.ParseName(PROPERTY_NAME).ToString())
                .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                .WithExpressionBody(arrowExpression)
                .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));

    ClassDeclarationSyntax classDefinition =
        SyntaxFactory
            .ClassDeclaration(
                SyntaxFactory
                    .Identifier(
                        SyntaxFactory
                            .ParseTypeName(CLASS_NAME).ToString()))
            .AddModifiers(
                SyntaxFactory.Token(SyntaxKind.PublicKeyword))
            .AddMembers(new[] { property });

    NamespaceDeclarationSyntax @namespace = SyntaxFactory
        .NamespaceDeclaration(
            SyntaxFactory
                .ParseName(NAMESPACE_NAME))
                .AddMembers(classDefinition)
        .NormalizeWhitespace();

    return SyntaxFactory
        .CompilationUnit()
        .WithMembers(
            SyntaxFactory
                .SingletonList<MemberDeclarationSyntax>(@namespace));
}

After compilation unit is prepared, I try to compile this and create instance of prepared class in 4 listed below ways:

  1. using nameof keyword

    1. get text from compilation unit SyntaxTree, then build new SyntaxTree based on this text, and compile new SyntaxTree -> It works -> I get a properly created instance

    2. build new SyntaxTree based on compilation unit SyntaxTree, and compile new SyntaxTree -> It doesn't works -> I get a "error CS0103: The name 'nameof' does not exist in the current context" during compilation

  2. using literal string

    1. get text from compilation unit SyntaxTree, then build new SyntaxTree based on this text, and compile new SyntaxTree -> It works -> i get a properly created instance

    2. build new SyntaxTree based on compilation unit SyntaxTree, and compile new SyntaxTree -> It works -> I get a properly created instance

Main function look like:

private static void Main(string[] args)
{
    CompilationUnitSyntax compilationUnit = PrepareCompilationUnit(useNameof: true);
    
    SaveToFile(compilationUnit);
    
    object test1 = CreateInstance(
        compilationUnit: compilationUnit,
        compileFromParsedString: true); // return instance
    object test2 = CreateInstance(
        compilationUnit: compilationUnit,
        compileFromParsedString: false); // return null
    
    compilationUnit = PrepareCompilationUnit(useNameof: false);
    
    SaveToFile(compilationUnit);
    
    test1 = CreateInstance(
        compilationUnit: compilationUnit,
        compileFromParsedString: true); // return instance
    test2 = CreateInstance(
        compilationUnit: compilationUnit,
        compileFromParsedString: false); // return instance
}

CreateInstance code:

private static object CreateInstance(
    CompilationUnitSyntax? compilationUnit,
    bool compileFromParsedString = false)
{
    object result = null;

    var options = new CSharpParseOptions(LanguageVersion.Latest);

    var syntaxTree = compileFromParsedString
        ? SyntaxFactory.ParseSyntaxTree(compilationUnit.ToFullString(), options)
        : SyntaxFactory.SyntaxTree(compilationUnit, options);

    var trustedAssembliesPaths = ((string)AppContext
        .GetData("TRUSTED_PLATFORM_ASSEMBLIES"))
        .Split(Path.PathSeparator);

    var neededAssemblies = new[]
    {
            typeof(object).Assembly.GetName().Name
    };
    List<PortableExecutableReference> references = trustedAssembliesPaths
        .Where(p => neededAssemblies.Contains(Path.GetFileNameWithoutExtension(p)))
        .Select(p => MetadataReference.CreateFromFile(p))
        .ToList();

    var compilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
        .WithOverflowChecks(true)
        .WithOptimizationLevel(OptimizationLevel.Debug);

    var compilation = CSharpCompilation
        .Create(Guid.NewGuid().ToString("N"), options: compilationOptions)
        .AddReferences(references)
        .AddSyntaxTrees(syntaxTree);

    try
    {
        using MemoryStream dllStream = new MemoryStream();
        Assembly assembly = null;
        EmitResult emitResult = compilation.Emit(dllStream);

        if (emitResult.Success)
        {
            assembly = Assembly.Load(dllStream.ToArray());
            result = assembly.CreateInstance($"{NAMESPACE_NAME}.{CLASS_NAME}");
        }
        else
        {
            foreach (var el in emitResult.Diagnostics)
            {
                Console.WriteLine(el);
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }

    return result;
}

I found, that the problem is on nameof expression creation. The problem is solved (create valid instance) as well when i use expression parser instead of creating invocationExpression manually:


var parsed = SyntaxFactory.ParseExpression($"nameof({PROPERTY_NAME})"))

var created = SyntaxFactory.InvocationExpression(
                SyntaxFactory.IdentifierName(
                        SyntaxFactory.Token(SyntaxKind.NameOfKeyword).ToString()),
                        SyntaxFactory.ArgumentList(
                            SyntaxFactory.SeparatedList(
                                new[] { SyntaxFactory
                                            .Argument(SyntaxFactory
                                                .IdentifierName(PROPERTY_NAME)) }))))

I realized that the problem is caused by RawContextualkind of node for IdentifierNameSyntax. It is SyntaxKind.NameOfKeyword for parsed expression, and SyntaxKind.IdentifierToken for created one.

Can anybody tell me how to create nameof expression without parsing a string (it seems to me as too hardcoded)?

Generated code (using nameof and string literal):

namespace namespaceExample
{
    public class ClsExample
    {
        public string TestName => nameof(TestName);
    }
}
namespace namespaceExample
{
    public class ClsExample
    {
        public string TestName => "TestName";
    }
}
Erjot
  • 21
  • 3

1 Answers1

0

I am writing a code generator (using Roslyn) which uses nameof. When I try to compile prepared SyntaxTree with Roslyn it fails with error "error CS0103: The name 'nameof' does not exist in the current context", whereas when I parse as SyntaxTree the full text of the same SyntaxTree compilation is done with no error.

That means you are creating a malformed tree that the compiler doesn't recognize. It's possible to artificially create different trees that correspond to the same text, but there is a single tree that is guaranteed to be correct, which is the form that is produced by the compiler when parsing that text.

The most easiest way to know how to construct a given tree given its string representation is by using Roslyn Quoter.

The correct code to create the nameof invocation syntax will be:

                                    SyntaxFactory.InvocationExpression(
                                        SyntaxFactory.IdentifierName(
                                            SyntaxFactory.Identifier(
                                                SyntaxFactory.TriviaList(),
                                                SyntaxKind.NameOfKeyword,
                                                "nameof",
                                                "nameof",
                                                SyntaxFactory.TriviaList())))
                                    .WithArgumentList(
                                        SyntaxFactory.ArgumentList(
                                            SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
                                                SyntaxFactory.Argument(
                                                    SyntaxFactory.IdentifierName(PROPERTY_NAME)))))
Youssef13
  • 3,836
  • 3
  • 24
  • 41