Skip to content

Overview

Haitch.Roslyn is a set of source-only utilities for Roslyn incremental source generators. The package ships .cs files that compile into your generator project as internal types, so nothing is added to your generator’s runtime dependencies.

It covers the parts of a generator that are easy to get subtly wrong: value-equal models so incremental caching works, a one-item-per-type attribute provider, cache-safe diagnostics, and indented source writing. A second, regular package, Haitch.Roslyn.Testing, provides test helpers.

  • C# 14 or later (<LangVersion>14</LangVersion> or latest), because the Polyfill dependency uses C# 14 extension blocks.
  • Your own reference to Microsoft.CodeAnalysis.CSharp 4.12.0 or later. Generator projects already have one; the package deliberately does not pass Roslyn on as a dependency.
  • Polyfill arrives as a transitive dependency, so there is nothing to add for it.

The package ships its source as contentFiles, which compile in netstandard2.0 generator projects.

Terminal window
dotnet add package Haitch.Roslyn --version 0.6.1
dotnet add package Haitch.Roslyn.Testing --version 0.6.1 # in your test project
NamespaceTypesPage
Haitch.Roslyn.TypesEquatableArray<T>Models
Haitch.Roslyn.ModelsTypeRef, AttributeModel, WellKnownAttributes, ConstantValue, TypeModel, NewTypeModel, ContainingTypeModel, member models (including EventModel), SyntaxInfo, BuildProperties, AdditionalFileModelModels
Haitch.Roslyn.GeneratorsForTypesWithAttribute, ForMethodsWithAttribute, ForPropertiesWithAttribute, ForFieldsWithAttribute, ForBuildProperty, ForBuildProperties, ForAdditionalFiles, AddMarkerAttribute, AddEmbeddedAttributeDefinition, HintNamePipeline helpers
Haitch.Roslyn.DiagnosticsLocationInfo, DiagnosticInfo, Result<T>, Result.Combine/Collect, ReportDiagnostics, PartialTypeValidationDiagnostics
Haitch.Roslyn.WritingSourceWriter, the typed scoped writer, attribute and new-type rendering, statement scopesSource writing, Attributes, new types and statements
Haitch.Roslyn.TestingGeneratorHarness, GeneratorHarnessInput, GeneratorHarnessResult and its assertions, CacheabilityOptions, CachingHazardWalkerTesting

For a complete generator that uses all of these, see the Walkthrough.

A bug-fix release. One change can break a caller: MethodModel has a new positional IsPartial parameter after IsPartialDefinition, so code that constructs it positionally must add it.

  • The writer throws ArgumentException for a method that is not an ordinary method or an explicit interface implementation, instead of writing output that does not compile. See Methods.
  • MethodModel.IsPartial is true for both parts of a partial method, and the writer emits partial from it, so a written implementation part compiles next to its definition. See Methods.
  • Diagnostics reported through the ReportDiagnostics pipeline extension are bound to the compilation’s syntax tree, so #pragma warning disable suppresses them. See ReportDiagnostics.

The Roslyn 4.12 floor is unchanged. The release lets member discovery see the sibling members of the containing type, and models C# 14 partial events. One change can break a caller: includeContainingTypeMembers sits before predicate on the three member providers, so a call that passed the predicate positionally must name it, as in predicate: ....

Pipeline

  • ForFieldsWithAttribute, ForPropertiesWithAttribute and ForMethodsWithAttribute take includeContainingTypeMembers (default false). When true, ContainingType carries fields, properties, methods, events and member names, so a generator can check for clashes without a second step. See Member discovery.

Models

  • TypeModel.MemberNames lists the distinct, ordinal-sorted name of every member, including nested types, accessors, indexers, constructors, operators and compiler-made members. It is filled only with includeMembers. See Member names.
  • A C# 14 partial event yields one EventModel with IsPartial set, and IsFieldLike is always false for it. See Base types, interfaces and events.

The Roslyn 4.12 floor is unchanged. The release finishes the testing harness and adds a raw-line writer for type bodies. Three changes can break a test: the harness overloads that took additionalReferences and parseOptions are gone, AssertCacheable now fails a step that holds the options provider by reference, and AdditionalTexts now holds AdditionalText, so write new HarnessAdditionalText(...) rather than target-typed new(...). HarnessAdditionalText is no longer a record, so it has no with, value equality or deconstruction.

Testing

  • The harness entry points are exactly Run(generator, GeneratorHarnessInput), Run(generator, params string[] sources), AssertCacheable(generator, GeneratorHarnessInput, steps, options) and AssertCacheable(generator, sources, params string[] trackedStepNames). Use GeneratorHarnessInput for references and parse options. See Testing generators.
  • GeneratorHarnessInput.AdditionalTexts takes any AdditionalText. UnreadableAdditionalText tests files the compiler cannot read, and HarnessAdditionalText is now a sealed class. See Additional texts.
  • Every AssertCacheable rerun uses a new options provider built from the same options, as the IDE does. See AssertCacheable.

Writing

  • TypeScope.Line writes a raw line, such as a #pragma, at the type body’s indent, and Line on every scope now defaults text to "". See Raw lines.

The Roslyn 4.12 floor is unchanged, and the 0.3 Run and AssertCacheable overloads still compile (removed in 0.5.0). The release serves generator authors end to end: testing with realistic input, finding members, and reading options.

Testing

  • GeneratorHarnessInput feeds additional files, global and per-file analyzer options, and an AllowInputErrors switch to the harness.
  • Diagnostic assertions (AssertNoDiagnostics, AssertDiagnostic) and expected-output assertions (AssertSource, AssertSourceFile, with HAITCH_ACCEPT=1). See Testing generators.

Pipeline

  • ForMethodsWithAttribute, ForPropertiesWithAttribute and ForFieldsWithAttribute, and an optional syntax predicate on all four For...WithAttribute methods. See Member discovery.
  • ForBuildProperty, ForBuildProperties and ForAdditionalFiles, which return equatable models. See Build properties and Additional files.

Models and writing

Smaller changes

  • EquatableArray<T> now constrains T : IEquatable<T>?, so EquatableArray<string?> works.
  • The method renderer writes abstract override for an abstract override.

The package is built against the C# 15 release candidate; the Roslyn 4.12 floor is unchanged. Behaviour changes from 0.2.0 come first.

Breaking and behaviour changes

  • Scoped-writer misuse now throws. The caller errors that 0.2.0 left to the compiler (writing to a parent while a child is open, chaining with a nested block open, a switch section that falls through, a bare try, a dangling Attribute()) are detected. They always throw, not only in debug builds. See Writer guards.
  • MethodModel.Name is the unqualified name for explicit interface implementations. It was System.IDisposable.Dispose; it is now Dispose, with the interface in ExplicitInterface. TypeModel.From(includeMembers: true) now includes explicit interface methods and properties, so Name is no longer unique within Methods or Properties.
  • WriteNewTypeDeclaration always opens a body. A positional record is written as record Person(string Name) { }; use the new WriteBodylessNewTypeDeclaration for the ; form.
  • TypeModel.From throws ArgumentException for an extension block.
  • TypeDeclarationKind has a new Union member. A switch over it needs a new arm, and a union is no longer reported as a struct.
  • Ref properties with set or init accessors are rejected by Property, and AutoProperty rejects any ref-returning property.

New

  • C# 15 unions and closed types in TypeModel and NewTypeModel, and labeled break/continue in the scoped writer. See C# 15 support.
  • TypeParameterModel.Variance and NewTypeModel.PrimaryConstructorParameters.
  • An optional CancellationToken on TypeModel.From and MethodModel.From, passed through by ForTypesWithAttribute.
  • FieldModel.IsVolatile and PropertyModel.ReturnRefKind.
  • HintName.For(..., disambiguateCase: true). See HintName.

A generator’s dependencies must be shipped and loaded inside the compiler host alongside the generator. Because the types here are compiled into your assembly as internal, there is no extra DLL to bundle, and no version clash when several generators in one build use different versions of the library.