roslyn-sdk icon indicating copy to clipboard operation
roslyn-sdk copied to clipboard

Weird behavior with Initialize

Open ghost opened this issue 3 years ago • 3 comments

Hi,

I hope this is the right repository to report the this issue.

I'm doing some test with code generator for a new project, I'm following the samples here:

https://github.com/dotnet/roslyn-sdk/tree/main/samples/CSharp/SourceGenerators/SourceGeneratorSamples

I saw in the documentation that Roslyn apparently works only for Visual Studio, nevertheless, I tried with Rider 2020.3 on my Mac, and I found that it works pretty fine.

In Rider the code looks broken before the first build, but after building the solution looks perfect as if the code were defined in the project, I can see the generated code.

The weird behavior occurs when I register a SyntaxReceiver in the Initialize method, the build still pass and the code is executed, but neither Rider nor in Visual Studio for Mac recognize the generated code.

This is my Target project

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
        <OutputType>Exe</OutputType>
        <LangVersion>9</LangVersion>
    </PropertyGroup>

    <PropertyGroup>
        <RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json ;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
    </PropertyGroup>

    <ItemGroup>
        <ProjectReference Include="..\Source\Source.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
    </ItemGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="all" />
        <PackageReference Include="Microsoft.Net.Compilers.Toolset" Version="3.10.0-1.final" PrivateAssets="all" />
    </ItemGroup>
</Project>

This is the Source project

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netstandard2.1</TargetFramework>
        <LangVersion>9</LangVersion>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.2" PrivateAssets="all" />
        <PackageReference Include="Microsoft.CodeAnalysis.Compilers" Version="3.10.0-1.final" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.10.0-1.final" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="3.10.0-1.final" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="3.10.0-1.final" />
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.10.0-1.final" />
        <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3" PrivateAssets="all" />
    </ItemGroup>

</Project>

This generated code work perfect

using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Source
{
    [Generator]
    public class HelloWorldGenerator : ISourceGenerator
    {
        public void Initialize(GeneratorInitializationContext context)
        {
            
        }

        public void Execute(GeneratorExecutionContext context)
        {
            // begin creating the source we'll inject into the users compilation
            var sourceBuilder = new StringBuilder(@"using System;

namespace HelloWorldGenerated
{
    public static class HelloWorld
    {
        public static void SayHello() 
        {
            Console.WriteLine(""Hello from generated code!"");
            Console.WriteLine(""The following syntax trees existed in the compilation that created this program:"");
");

            // using the context, get a list of syntax trees in the users compilation
            var syntaxTrees = context.Compilation.SyntaxTrees;

            // add the filepath of each tree to the class we're building
            foreach (SyntaxTree tree in syntaxTrees)
            {
                sourceBuilder.AppendLine($@"            Console.WriteLine(@"" - {tree.FilePath}"");");
            }

            // finish creating the source to inject
            sourceBuilder.Append(@"
        }
    }
}");

            // inject the created source into the users compilation
            context.AddSource("HelloWorld.Generated.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));

        }
    }
}

This is the code with the problem

using System;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace Source
{
    [Generator]
    public class DisplayStringGenerator : ISourceGenerator
    {
        const string DisplayStringAttributeCode = @"using System;

namespace ModelUtils
{
    [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
    public class DisplayStringAttribute : Attribute {
        public DisplayStringAttribute() { }
        public string Exclude { get; set; }
    }
}
";

        public void Initialize(GeneratorInitializationContext context)
        {
            
            context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
        }

        public void Execute(GeneratorExecutionContext context)
        {
            GenerateAttribute(context);
            ProcessClass(context);
            
        }

        private void GenerateAttribute(GeneratorExecutionContext context)
        {
            context.AddSource("DisplayStringAttribute.Generated.cs",
                SourceText.From(new StringBuilder(DisplayStringAttributeCode).ToString(), Encoding.UTF8));
        }

        private void ProcessClass(GeneratorExecutionContext context)
        {
        }

        class SyntaxReceiver : ISyntaxContextReceiver
        {
            public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
            {
                //TODO: Implement it
            }
        }
    }
}

I moved the Attribute code to the Execute method to make it work, because adding the code through the Initialize method has the same weird behavior.

I'm not sure if this is a bug or I'm missing something in my code.

ghost avatar Mar 08 '21 17:03 ghost

@ovargasmahisoft were you trying to add the attribute code using context.RegisterForPostInitialization as the AutoNotify sample does? If so that's only supported in VS16.9+

chsienki avatar Mar 08 '21 23:03 chsienki

Hi @chsienki thanks for your response.

Yes I tried that way and the code builds and runs fine but the IDE doesn't recognize it, it is ok if this is a IDE issue for now. What I find weird, is that even the HelloWorld sample fails to recognize the code just after registering a SyntaxReceiver.

ghost avatar Mar 09 '21 01:03 ghost

when I register a SyntaxReceiver in the Initialize method

You definitely don't want to do this. There is no way to use RegisterForSyntaxNotifications from the V1 API without causing an overwhelming performance hit for anything but the smallest of solutions. For syntax callbacks, you'll want to rewrite the source generator using IIncrementalGenerator which was added in Roslyn 4.0 and resolves this problem.

We tried hard to find viable workarounds that allow SyntaxReceiver to work without problems, but were never successful.

sharwell avatar Feb 02 '22 17:02 sharwell