Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiling a Syntax Tree using Roslyn

I'm trying to use Roslyn to generate and compile a runtime library of simple objects containing get/set properties.

However, for some reason, compiling the assembly fails with error of adding the Linq namespace (error CS0246: The type or namespace name 'System.Linq' could not be found (are you missing a using directive or an assembly reference?)}).

I've tried manipulating the generated tree in a number of ways and compiling each, but still compilation fails.

The only way in which compilation succeeds is if the tree is parsed to a string, then parsed back to a syntax tree and then compiled.

The code below does the following:

  1. Build a simple syntax tree containing compilation unit, usings, namespace, class and property.
  2. Try to compile the tree (fails)
  3. Generate new Syntax Tree with C#6 option and compile (fails)
  4. Format Syntax Tree and compile (fails)
  5. Serialize tree to string, then use SyntaxFactory.ParseSyntaxTree and compile the generated tree (success)

The code:

    private static readonly CSharpCompilationOptions DefaultCompilationOptions =
        new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
                .WithOverflowChecks(true)
                .WithPlatform(Platform.X86)
                .WithOptimizationLevel(OptimizationLevel.Release)
                .WithUsings(DefaultNamespaces);
    private static readonly IEnumerable<string> DefaultNamespaces =
        new[]
        {
                    "System",
                    "System.IO",
                    "System.Net",
                    "System.Linq",
                    "System.Text",
                    "System.Text.RegularExpressions"
        };

    private static readonly IEnumerable<MetadataReference> DefaultReferences =
        new[]
        {
                    MetadataReference.CreateFromFile(typeof (object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof (System.Linq.Enumerable).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof (System.GenericUriParser).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.Location)
        };

    static void Main(string[] args)
    {
        MakeAssembly();
        Console.ReadLine();
    }

    private static void MakeAssembly()
    {
        //Compilation Unit and Usings
        CompilationUnitSyntax cu = SyntaxFactory.CompilationUnit()
            .AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName("System")),
            SyntaxFactory.UsingDirective(SyntaxFactory.IdentifierName(typeof(System.Linq.Enumerable).Namespace)))
        ;

        // NameSpace
        NamespaceDeclarationSyntax ns = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.IdentifierName("Roslyn"));

        // Class
        ClassDeclarationSyntax classNode = SyntaxFactory.ClassDeclaration("MyClass")
                        .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword))
                    ;

        // Property
        classNode= classNode.AddMembers(
                                SyntaxFactory.PropertyDeclaration(SyntaxFactory.ParseTypeName("Int32"), "MyProperty")
                                        .AddAccessorListAccessors(
                                        SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                                        SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))).
                                        AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)));
        ns = ns.AddMembers(classNode);
        cu = cu.AddMembers(ns);

        // Try To Compile Syntax Tree root
        var root = cu.SyntaxTree.GetRoot();
        var st = root.SyntaxTree;
        var assembly = CompileAndLoad(st);

        if (assembly != null)
        {
            Console.WriteLine("Success compile syntax tree root");
            return;
        }
        else
            Console.WriteLine("failed to compile syntax tree root");

        // Try to compile new syntax tree
        var stNew = SyntaxFactory.SyntaxTree(cu, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
        assembly = CompileAndLoad(stNew);
        if (assembly != null)
        {
            Console.WriteLine("Success compile new syntax tree");
            return;
        }
        else
            Console.WriteLine("failed to compile new syntax tree");

        // Try to format node
        AdhocWorkspace cw = new AdhocWorkspace();
        OptionSet options = cw.Options;
        options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, false);
        options = options.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInTypes, false);

        SyntaxNode formattedNode = Formatter.Format(cu, cw, options);
        var stFormat = SyntaxFactory.SyntaxTree(cu, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp6));
        assembly = CompileAndLoad(stFormat);
        if (assembly != null)
        {
            Console.WriteLine("Success compile formatted syntax tree");
            return;
        }
        else
            Console.WriteLine("failed to compile formatted syntax tree");


        // Try to serialize and parse
        StringBuilder sb = new StringBuilder();
        using (StringWriter writer = new StringWriter(sb))
        {
            formattedNode.WriteTo(writer);
        }
        var treeAsString = sb.ToString();
        var stParsed = SyntaxFactory.ParseSyntaxTree(treeAsString);
        assembly = CompileAndLoad(stParsed);
        if (assembly != null)
        {
            Console.WriteLine("Success compile parsed syntax tree");
            return;
        }   
        else
            Console.WriteLine("failed to compile formatted syntax tree");

    }

    private static Assembly CompileAndLoad(SyntaxTree st)
    {
        var compilation
            = CSharpCompilation.Create("TestRoslyn.dll", new SyntaxTree[] { st }, null, DefaultCompilationOptions);
        compilation = compilation.WithReferences(DefaultReferences);
        using (var stream = new MemoryStream())
        {
            EmitResult result = compilation.Emit(stream);
            if (result.Success)
            {
                var assembly = Assembly.Load(stream.GetBuffer());
                return assembly;
            }
            return null;
        }
    }
like image 587
Avishay Balter Avatar asked Apr 25 '16 07:04

Avishay Balter


People also ask

What is Roslyn compiler .NET core?

Roslyn, the . NET Compiler Platform, empowers the C# compiler on . NET Core and allows developers to leverage the rich code analysis APIs to perform code generation, analysis and compilation.

What is Roslyn API?

NET Compiler Platform, also known by its codename Roslyn, is a set of open-source compilers and code analysis APIs for C# and Visual Basic (VB.NET) languages from Microsoft.

What is Roslyn dotnet?

The . NET Compiler Platform ("Roslyn") provides open-source C# and Visual Basic compilers with rich code analysis APIs. You can build code analysis tools with the same APIs that Microsoft is using to implement Visual Studio!


2 Answers

I fell into this trap with Roslyn also. The using directive is not just expressed as a string each part of the qualified name is a syntax node. You need to create your node like this

var qualifiedName= SyntaxFactory.QualifiedName(SyntaxFactory.IdentifierName("System"),    
                                                 SyntaxFactory.IdentifierName("Linq"));     
var usingDirective = SyntaxFactory.UsingDirective(qualifedName);

I wrote a helper method to convert a string to the correct syntax node.

private UsingDirectiveSyntax CreateUsingDirective(string usingName)
{
    NameSyntax qualifiedName = null;

    foreach (var identifier in usingName.Split('.'))
    {
        var name = SyntaxFactory.IdentifierName(identifier);

        if (qualifiedName != null)
        {
            qualifiedName = SyntaxFactory.QualifiedName(qualifiedName, name);
        }
        else
        {
            qualifiedName = name;
        }
    }

    return SyntaxFactory.UsingDirective(qualifiedName);
}
like image 197
user2697817 Avatar answered Sep 27 '22 22:09

user2697817


You can use SyntaxFactory.ParseName which will handle parsing the string and then building the qualified name syntax node for your using directives:

var qualifiedName = SyntaxFactory.ParseName("System.Linq");
var usingDirective = SyntaxFactory.UsingDirective(qualifiedName);
like image 42
Ray Avatar answered Sep 27 '22 21:09

Ray