How can I make an incremental source generator run in Visual Studio?
Asked Answered
I

1

9

I've followed the documentation to create a basic incremental source generator that outputs a copy of classes marked with an attribute:

// Code found in my project
[Copy]
public class MyClass 
{ 
    public int Value { get; set; }
}

// Output from the source generator
public class Copy_MyClass 
{ 
    public int Value { get; set; }
}

The source generation itself works perfectly fine, but I've run into two problems when using it with Visual Studio 2022:

  • The source generator only executes during builds.
  • Intellisense doesn't pick up the generated code until I restart Visual Studio.
    • I can work around this by having the generated source files written to the project directory, but it's not ideal and still suffers from the above issue: changes don't reflect until I try to build.

For example, if I create a new class Foo with the [Copy] attribute and then try to reference Copy_Foo in my code, Visual Studio will display an error that Copy_Foo is undefined.

[Copy]
public class Foo { }

public static class TestCopy
{
    public static void Test()
    {
        Copy_Foo foo = new Copy_Foo();
//      ^^^^^^^^ 
//      The type or namespace 'Copy_Foo' could not be found
    }
}

Building this code will succeed, but Visual Studio will continue to think that Copy_Foo doesn't exist. Once I restart, it will know that Copy_Foo exists, but any changes to it won't be picked up until I restart again.

I've created a small example, which outputs only the [Copy] attribute.

The first project contains the source generator:

// TestSourceGenerator.csproj
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
        <IncludeBuildOutput>false</IncludeBuildOutput>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3">
            <PrivateAssets>all</PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
        </PackageReference>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
    </ItemGroup>

    <ItemGroup>
        <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
    </ItemGroup>

</Project>
// TestSourceGenerator.cs
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;

namespace TestSourceGenerator
{
    [Generator]
    public class TestSourceGenerator : IIncrementalGenerator
    {
        private const string CopyAttributeSource = @"
namespace Generated
{
    public class CopyAttribute : System.Attribute { }
}";

        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            context.RegisterPostInitializationOutput(ctx => ctx.AddSource("CopyAttribute.g.cs", SourceText.From(CopyAttributeSource, Encoding.UTF8)));
        }
    }
}

And the second consumes the generator:

// TestSourceGenerator.Test.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Library</OutputType>
    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

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

</Project>
// MyClass.cs
namespace TestSourceGenerator.Test
{
    [Generated.CopyAttribute]
    public class MyClass
    {
    }
}

If you create the TestSourceGenerator.Test project without the ProjectReference, open it in Visual Studio, and then edit the project file to reference the analyzer, you'll see that Visual Studio displays an error on [Generated.CopyAttribute], but the project builds successfully.

Is there any way to make Visual Studio both run my source generators in the IDE and pick up symbols from the generated code? According to the documentation, it seems like this is supposed to be a supported use case, and even one of the primary motivations for incremental source generators existing.

Incoercible answered 26/12, 2021 at 0:10 Comment(3)
I have had a similar experience, but the generator works for a manual test run. #71079529Caisson
Did you ever find a solution? Based on what the docs say about caching incremental results, what seems to be happening is that Intellisense forgets everything whenever the predicate passed to CreateSyntaxProvider produces a different set of syntax elements.Finned
I'm trying to make it run only during a build. You can verify it runs on every key press in the IDE by using the test code in Pawel Gerr's article.Drysalter
C
1

There are typically two reasons for your struggles, and both are frustrating if not well understood.

One issue is that Visual Studio does not allow for analyzer assemblies to be unloaded once they are loaded. Once Visual Studio has loaded your analyzer for use in the IDE and Intellisense, it will keep using that version until you close Visual Studio, or at least until you increase the assembly version. However, when you hit build/rebuild for your project Visual Studio will spawn a new msbuild process which will (typically) load a fresh version of your analyzer. Thus you may end up with a project that builds fine but doesn't update the IDE and Intellisense. This is actually an old problem, and not directly related to generators, see https://github.com/dotnet/roslyn/issues/48083 for more information. (One recommendation is to develop your analyzer not "live" through Visual Studio but with the help of Unit Tests.)

Another cache issue concerns incremental builds with IIncrementalGenerator but I'm not sure this is relevant for the code you posted. Anyhow, this newer version of the source generator will, if you play it right, cache the last execution and reuse the output for the IDE/Intellisense if nothing related has changed. This typically requires you to implement a custom equality comparer for the content of the source syntax node. However, if this comparison fails to take into account the relevant content (i.e. that which actually changed with the last keypress) the generator will not be executed and the IDE/Intellisense will not be updated. Again, msbuild may still run fine because each new build ignores any previous output cache and just feeds the analyzer every source node from the start.

Cheloid answered 2/1, 2023 at 22:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.