Incremental source generators

Generate code, to be used in code, to generate additional code

Home DailyDrop

Daily Knowledge Drop

Incremental source generators can be used to generated fixed code, which can be used in user code, which can then be used by the source generator to generate additional code.

This is done through the usage of RegisterPostInitializationOutput available to incremental generators.

This will make more sense as we look at an example. This example below is an extension of the example in last week's "emitting source generated files" post.


Source generators

In short, a source generator is a piece of code which writes code. The functionality ships as part of Roslyn, the .NET Compiler Platform SDK.

Source generators allow for the inspection of user code as compile time, and then based on specific criteria, can add additional code to the original code base, on the fly.

Let's look at a very simple example, and all it's moving parts. In the example, based on a marker attribute being present on a class, an additional method called "WhoAmI" will be added to the class, which will return the class the method is a member of.

Attribute declaration

One big negative aspect of the above approach is where is the marker attribute mentioned declared?

The source generator is going to look for the marker attribute (called WhoAmIAttribute in this example), and then generate the additional source code based on this attribute. Generally there were two options for the location of this attribute:

  • Make it a requirement that the user creates the attribute. The source generator will only look for the attribute by name, so as long as its named correctly, all is good
  • Reference the attribute in another external dll/NuGet package

The incremental generator introduced in .NET6 provides another, (in some situations) more convenient way.


Incremental generator

Incremental generators are similar to normal source generators, but provide a hook which allows source code to be generated post initialization, before the full source generators execute.

Basically, source code can be generated post initialization of the generator, separate to the actual code generated by the source generator.

Let's have a look at the incremental generator for the scenario described above.

// Instead of implementing ISourceGenerator, 
// the generator implements IIncrementalGenerator
[Generator]
public class WhoAmIIncrementalGenerator : IIncrementalGenerator
{
    // Instead of a GeneratorInitializationContext, the initialize method 
    // takes an IncrementalGeneratorInitializationContext parameter
    public void Initialize(IncrementalGeneratorInitializationContext context)
    {
        // Register the attribute source
        context.RegisterPostInitializationOutput(i =>
        {
            var attributeSource = @"
namespace AutoGenerated
{
    [AttributeUsage(AttributeTargets.Class)]
    public class WhoAmIAttribute : Attribute
    {
        public WhoAmIAttribute() { }
    }
}";

            i.AddSource("WhoAmIAttribute.g.cs", attributeSource);
        });
    }
}

Generation process

The source generator and syntax receiver defined in the "emitting source generated files" post, stay exactly the same.

Prior to the introduction of the incremental generator, the generation process would have been:

  1. Source generator(s) are initialized
  2. Marker attribute definition needs to be present (either by user or in an external library)
  3. Marker attribute is used in user code
  4. Source generator(s) are executed and scans user code for the marker attribute usage
  5. Relevant additional code is generated and output if marker attribute is found

However with the introduction of the incremental generator, the process changes slightly.

  1. Source generator(s) are initialized
  2. Incremental generator creates the marker attribute, this is now available to the user
  3. Marker attribute is used in user code
  4. Source generator(s) are executed and scans user code for the marker attribute
  5. Relevant additional code is generated and output if marker attribute is found

Constraints

There are some constraints when using incremental generators:

  • They cannot access user code - incremental generators do not have access to the user code base, and therefore can only generate fixed code
  • Multiple project referencing issues - as the code being generated is fixed, if multiple projects in the solution reference the analyzer, the fixed code will be generated multiple times, causing the solution to be unable to compile

Notes

Even with the limitations (for which there are techniques to over come) incremental generators are a useful tool when some pre-generator-execution logic need to occur.


References

Source Generators
Solving the source generator 'marker attribute' problem - Part 1
Solving the source generator 'marker attribute' problem - Part 2

Daily Drop 24: 04-03-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net roslyn sourcegenerator generator incremental incrementalgenerator