FractalDataWorks.Collections.SourceGenerators 0.6.0-rc.1

This is a prerelease version of FractalDataWorks.Collections.SourceGenerators.
dotnet add package FractalDataWorks.Collections.SourceGenerators --version 0.6.0-rc.1
                    
NuGet\Install-Package FractalDataWorks.Collections.SourceGenerators -Version 0.6.0-rc.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FractalDataWorks.Collections.SourceGenerators" Version="0.6.0-rc.1">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FractalDataWorks.Collections.SourceGenerators" Version="0.6.0-rc.1" />
                    
Directory.Packages.props
<PackageReference Include="FractalDataWorks.Collections.SourceGenerators">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FractalDataWorks.Collections.SourceGenerators --version 0.6.0-rc.1
                    
#r "nuget: FractalDataWorks.Collections.SourceGenerators, 0.6.0-rc.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FractalDataWorks.Collections.SourceGenerators@0.6.0-rc.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FractalDataWorks.Collections.SourceGenerators&version=0.6.0-rc.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FractalDataWorks.Collections.SourceGenerators&version=0.6.0-rc.1&prerelease
                    
Install as a Cake Tool

FractalDataWorks.Collections.SourceGenerators

Source generator for FractalDataWorks.Collections that creates type collections with compile-time discovery and O(1) lookup performance using .NET 8+ FrozenDictionary.

⚠️ CRITICAL: Required Architecture Pattern

ALL TypeCollections MUST follow the 4-component pattern. The source generator will NOT work without all components properly configured.

The 4 Required Components

  1. Interface extending ITypeOption<TKey, T> - where TKey is the ID type and T is the interface itself
  2. Base class inheriting TypeOptionBase<TKey, T> AND implementing interface
  3. TypeCollection with [TypeCollection] attribute
  4. TypeOptions with [TypeOption] attribute

Complete Correct Example

From ISecurityMethod.cs:9 and SecurityMethodBase.cs:9:

// 1. REQUIRED: Interface extends ITypeOption<TKey, T>
public interface ISecurityMethod : ITypeOption<int, ISecurityMethod>
{
    bool RequiresAuthentication { get; }
    string? AuthenticationScheme { get; }
    bool SupportsTokenRefresh { get; }
}

// 2. REQUIRED: Base class inherits TypeOptionBase<TKey, T> AND implements interface
public abstract class SecurityMethodBase : TypeOptionBase<int, ISecurityMethod>, ISecurityMethod
{
    protected SecurityMethodBase(int id, string name, bool requiresAuthentication,
        string? authenticationScheme, bool supportsTokenRefresh)
        : base(id, name)
    {
        RequiresAuthentication = requiresAuthentication;
        AuthenticationScheme = authenticationScheme;
        SupportsTokenRefresh = supportsTokenRefresh;
    }

    public bool RequiresAuthentication { get; }
    public string? AuthenticationScheme { get; }
    public bool SupportsTokenRefresh { get; }
}

// 3. REQUIRED: TypeCollection with correct attribute parameters
[TypeCollection(typeof(SecurityMethodBase), typeof(ISecurityMethod), typeof(SecurityMethods))]
public partial class SecurityMethods : TypeCollectionBase<SecurityMethodBase, ISecurityMethod>
{
}

// 4. REQUIRED: TypeOptions with [TypeOption] attribute
[TypeOption(typeof(SecurityMethods), "ApiKey")]
public sealed class ApiKey : SecurityMethodBase
{
    public ApiKey() : base(3, "ApiKey", requiresAuthentication: true,
        authenticationScheme: "ApiKey", supportsTokenRefresh: false) { }
}

See FractalDataWorks.Collections README.md for complete architecture requirements.

⚠️ CRITICAL: Cross-Project Source Generator Configuration

For TypeCollections to work across projects, proper project references are REQUIRED.

See Source Generator Packaging & Distribution Guide for comprehensive documentation on generator packaging, ILRepack configuration, NuGet distribution, and troubleshooting.

Abstractions Project (Where TypeCollection Lives)

<ItemGroup>
  <PackageReference Include="FractalDataWorks.Collections" Version="*" />
  <PackageReference Include="FractalDataWorks.Collections.SourceGenerators" Version="*">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  </PackageReference>
</ItemGroup>

Implementation Project (Where TypeOptions Live)

CRITICAL: This project MUST properly reference and embed the source generator.

<ItemGroup>
  
  <ProjectReference Include="..\Project.Abstractions\Project.Abstractions.csproj" />

  
  <ProjectReference Include="..\FractalDataWorks.Collections.SourceGenerators\FractalDataWorks.Collections.SourceGenerators.csproj"
                    OutputItemType="Analyzer"
                    ReferenceOutputAssembly="false"
                    PrivateAssets="none" />

  
  <None Include="..\FractalDataWorks.Collections.SourceGenerators\bin\$(Configuration)\netstandard2.0\FractalDataWorks.Collections.SourceGenerators.dll"
        Pack="true"
        PackagePath="analyzers/dotnet/cs"
        Visible="false"
        Condition="Exists('..\FractalDataWorks.Collections.SourceGenerators\bin\$(Configuration)\netstandard2.0\FractalDataWorks.Collections.SourceGenerators.dll')" />
</ItemGroup>

Critical Settings Explained:

Setting Value Why
OutputItemType "Analyzer" Makes project reference act as source generator
ReferenceOutputAssembly "false" Don't include DLL in compilation (analyzer only)
PrivateAssets "none" CRITICAL: Propagates generator to consuming projects
<None Include=...> Generator DLL Embeds generator so consumers see generated code

Common Mistake: Using PrivateAssets="all" prevents consumers from seeing generated code like ConnectionStates.Open.

Consumer Projects

<ItemGroup>
  
  <ProjectReference Include="..\Project\Project.csproj" />

  
  
</ItemGroup>

Architecture Pattern

TypeCollections enable cross-project extensible type discovery where:

  • Collections are placed in abstractions projects for maximum discoverability
  • Base Types are in concrete projects for implementation inheritance
  • Type Options can be added by downstream developers in any project

Project Structure

FractalDataWorks.Web.Http.Abstractions/
├── Security/
│   ├── SecurityMethods.cs              <- Collection (abstractions for discoverability)
│   └── ISecurityMethod.cs              <- Interface
FractalDataWorks.Web.Http/
├── Security/
│   └── SecurityMethodBase.cs           <- Base Type (concrete project)
Any.Implementation.Project/
├── CustomSecurityMethod.cs             <- Options (can be added anywhere)

Overview

This source generator analyzes your code at compile time to:

  1. TypeOption-First Discovery: Discovers types with [TypeOption] attributes and groups by target collection
  2. Universal Type Support: Includes concrete, abstract, static, and interface types in collections
  3. Cross-Project Extensibility: Collections in abstractions enable downstream option discovery
  4. Smart Instantiation: Only instantiates concrete types, safely handles abstract/static types
  5. Property-Based Lookup: O(1) property-based lookups using dedicated FrozenDictionary for each lookup property

Installation

dotnet add package FractalDataWorks.Collections.SourceGenerators

For .Abstractions projects, use analyzer-only packaging:

<PackageReference Include="FractalDataWorks.Collections.SourceGenerators" Version="*">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

How It Works

Discovery Process

  1. TypeOption-First Discovery

    [TypeOption(typeof(DataContainerTypes), "CSV")]
    public class CsvDataContainerType : DataContainerType { }
    
    [TypeOption(typeof(DataContainerTypes), "JSON")]
    public class JsonDataContainerType : DataContainerType { }
    
    [TypeOption(typeof(DataContainerTypes), "BaseContainer")] // Abstract types included in collection
    public abstract class BaseDataContainerType : DataContainerType { }
    
    [TypeOption(typeof(DataContainerTypes), "UtilityContainer")] // Static types included in collection
    public static class UtilityDataContainerType : DataContainerType { }
    
    • STEP 1: Single pass through all assemblies to find [TypeOption] attributes with explicit collection targeting
    • STEP 2: Group discovered types by their target collection type (from TypeOption parameter)
    • Includes ALL types: concrete, abstract, static, and interface types with [TypeOption]
    • Smart Instantiation: Only instantiates concrete types that can be created with new
    • Explicit Targeting: Each type explicitly declares which collection it belongs to via [TypeOption(typeof(CollectionType), "Name")]
    • No Inheritance Scanning: No need to scan inheritance hierarchies for collection discovery
  2. Collection Discovery (Attribute-Based)

    From FederationStrategies.cs:31:

    // In Abstractions Project - for cross-project discoverability
    [TypeCollection(typeof(FederationStrategyBase), typeof(IFederationStrategy), typeof(FederationStrategies))]
    public abstract partial class FederationStrategies : TypeCollectionBase<FederationStrategyBase, IFederationStrategy>
    {
    }
    
    • STEP 3: Find [TypeCollection] attributes in abstractions projects
    • STEP 4: Match pre-discovered TypeOption types by their target collection type
    • Cross-Project Discovery: Collections in abstractions can discover options from any referenced assembly
  3. Code Generation with .NET 8+ Optimizations

    • Creates partial class implementations with FrozenDictionary<int, T> primary storage
    • Dynamic TypeLookup: Generates lookup methods based on [TypeLookup] attributes in base class
    • Property-Based Lookup: Uses dedicated FrozenDictionary for each lookup property for O(1) performance
    • Clean Method Names: Id(5) and Category("file") instead of GetById() and GetByCategory()
    • Generates NotFound sentinel implementations with intelligent defaults

Generated Code Structure

Complete Architecture Example

From TypeOptionBase.cs:12-88:

// TypeOptionBase already provides [TypeLookup] on Id and Name properties:
public abstract class TypeOptionBase<TKey, T> : ITypeOption<TKey, T>
    where TKey : IEquatable<TKey>
    where T : ITypeOption<TKey, T>
{
    [TypeLookup("ById")]  // Generates ById(TKey id) method
    public virtual TKey Id { get; }

    [TypeLookup("ByName")]  // Generates ByName(string name) method
    public string Name { get; }

    public string Category { get; }

    protected TypeOptionBase(TKey id, string name) : this(id, name, string.Empty) { }
    protected TypeOptionBase(TKey id, string name, string? category) { ... }
}

From SimpleCollection.cs:9-29:

// 1. Base type inherits TypeOptionBase<TKey, T>
public abstract class StatusBase : TypeOptionBase<int, StatusBase>
{
    protected StatusBase(int id, string name) : base(id, name) { }
}

// 2. TypeCollection with correct attribute parameters
[TypeCollection(typeof(StatusBase), typeof(StatusBase), typeof(Statuses))]
public partial class Statuses : TypeCollectionBase<StatusBase, StatusBase>
{
}

// 3. TypeOptions with [TypeOption] attribute
[TypeOption(typeof(Statuses), "Open")]
public class OpenStatus : StatusBase
{
    public OpenStatus() : base(1, "Open") { }
}

[TypeOption(typeof(Statuses), "Closed")]
public class ClosedStatus : StatusBase
{
    public ClosedStatus() : base(2, "Closed") { }
}

The generator creates (simplified from TypeCollectionGenerator.cs:280-590):

public partial class Statuses
{
    // Pending registrations and frozen state
    private static readonly List<StatusBase> _pendingRegistrations = new();
    private static readonly object _lock = new();
    private static volatile bool _frozen;
    private static StatusBase[]? _all;

    // Frozen dictionaries for lookups (populated on first access)
    private static FrozenDictionary<int, StatusBase>? _byId;
    private static FrozenDictionary<string, StatusBase>? _byName;

    // Static constructor registers compile-time discovered options
    static Statuses()
    {
        RegisterMember(new OpenStatus());
        RegisterMember(new ClosedStatus());
    }

    // RegisterMember allows runtime registration before freeze
    public static void RegisterMember(StatusBase type)
    {
        if (_frozen)
            throw new InvalidOperationException("Collection is frozen");
        lock (_lock)
        {
            if (!_pendingRegistrations.Any(p => p.GetType() == type.GetType()))
                _pendingRegistrations.Add(type);
        }
    }

    // EnsureFrozen freezes on first access
    private static void EnsureFrozen()
    {
        if (_all != null) return;
        lock (_lock)
        {
            if (_all != null) return;
            _frozen = true;
            var items = _pendingRegistrations.ToArray();
            _all = items;
            _byId = items.ToFrozenDictionary(x => x.Id);
            _byName = items.ToFrozenDictionary(x => x.Name);
        }
    }

    // Static property accessors for each discovered type
    private static OpenStatus? _open;
    public static OpenStatus Open
    {
        get
        {
            EnsureFrozen();
            return _open ??= (OpenStatus)_all!.First(x => x.Name == "Open");
        }
    }

    // Lookup methods from [TypeLookup] attributes
    public static StatusBase ById(int value)
    {
        EnsureFrozen();
        return _byId!.TryGetValue(value, out var result) ? result : NotFound;
    }

    public static StatusBase ByName(string? name)
    {
        EnsureFrozen();
        if (string.IsNullOrEmpty(name)) return NotFound;
        return _byName!.TryGetValue(name, out var result) ? result : NotFound;
    }

    // All() returns all registered options
    public static IReadOnlyCollection<StatusBase> All()
    {
        EnsureFrozen();
        return _all!;
    }

    // NotFound sentinel
    private static readonly StatusBase _notFound = new NotFoundStatuses();
    public static StatusBase NotFound => _notFound;

    internal sealed class NotFoundStatuses : StatusBase
    {
        internal NotFoundStatuses() : base(0, string.Empty) { }
    }
}

Incremental Generation & Caching

The generator uses Roslyn's Incremental Generator API with hash-based caching for optimal build performance:

How It Works

  1. Providers Cache Discoveries: TypeOptions and TypeCollections are discovered and cached by Roslyn
  2. Record Struct Models: Models use record struct for value-based equality comparison
  3. Selective Regeneration: Only regenerates when semantic changes occur

Model-Based Change Detection

From Models.cs:23-35:

// The generator uses record structs for model comparison
internal readonly record struct TypeCollectionModel(
    string ClassName,
    string Namespace,
    string FullName,
    string BaseTypeName,
    string InterfaceTypeName,
    string MatchKey,
    CollectionKind Kind,
    bool RestrictToCurrentCompilation,
    string? ParentCollectionMatchKey,
    string? ChildName,
    ImmutableArray<ParameterModel> BaseConstructorParameters
);

Record structs provide value-based equality, enabling Roslyn to skip regeneration when model properties are unchanged.

What Triggers Regeneration:

  • ✅ Adding/removing TypeOptions
  • ✅ Changing property types
  • ✅ Modifying attribute values
  • ✅ Changing method signatures

What Doesn't Trigger Regeneration:

  • ❌ XML documentation changes
  • ❌ Code comments
  • ❌ Whitespace/formatting
  • ❌ File renames (same content)

Result: Only affected collections regenerate.

Performance Characteristics

Runtime Performance

  • FrozenDictionary: O(1) lookups using .NET 8+ FrozenDictionary (falls back to ImmutableDictionary on netstandard2.0)
  • Property-Based Lookup: Dedicated FrozenDictionary for each [TypeLookup] property
  • Deferred Freeze: Collection freezes on first access, allowing cross-assembly registration
  • NotFound Pattern: Returns cached NotFound sentinel instead of null

Collection Access Patterns

  • TypeCollectionBase<TBase>: Single generic for simple scenarios
  • TypeCollectionBase<TBase, TGeneric>: Dual generic when return type differs from base type
  • All() Method: Returns IReadOnlyCollection<T> backed by frozen array

Compilation Performance

  • Explicit Collection Targeting: Each type declares its target collection via [TypeOption] attribute
  • TypeOption-First Discovery: Scans for [TypeOption] attributes, groups by target collection
  • Single-Pass Assembly Scanning: One scan to find all [TypeOption] attributes
  • Incremental Generation: Only regenerates when source changes

Validation and Diagnostics

Generator Diagnostics

From Diagnostics.cs:10-130:

TC001: Id Hash Collision
// Error: Multiple TypeOptions have the same auto-generated Id
[TypeOption(typeof(Statuses), "Active")]
public class ActiveStatus : StatusBase { public ActiveStatus() : base(1, "Active") { } }

[TypeOption(typeof(Statuses), "Running")]
public class RunningStatus : StatusBase { public RunningStatus() : base(1, "Running") { } } // Same Id!
TC002: Interface Not Implemented
// Error: TypeOption must implement the interface from [TypeCollection]
[TypeOption(typeof(SecurityMethods), "Custom")]
public class CustomMethod : SecurityMethodBase // Must implement ISecurityMethod
{
}
TC007: Duplicate Option Name
// Error: TypeOption names must be unique within a collection
[TypeOption(typeof(Statuses), "Open")]
public class OpenStatus : StatusBase { }

[TypeOption(typeof(Statuses), "Open")] // Duplicate name!
public class AnotherOpenStatus : StatusBase { }

Parent-Child Collection Relationships

TypeCollections can be organized into parent-child hierarchies using the TypeOption and TypeOptionName properties on the [TypeCollection] attribute. This enables nested collections where child collections register themselves with a parent.

From TypeCollectionAttribute.cs:97-134:

// Parent collection
[TypeCollection(typeof(DataTypeConverterCollectionBase),
                typeof(IDataTypeConverters),
                typeof(DataTypeConverters))]
public partial class DataTypeConverters : TypeCollectionBase<DataTypeConverterCollectionBase, IDataTypeConverters>
{
}

// Child collection - registers with parent via TypeOption/TypeOptionName
[TypeCollection(typeof(DataTypeConverterBase),
                typeof(IDataTypeConverter),
                typeof(MsSqlDataTypeConverters),
                TypeOption = typeof(DataTypeConverters),
                TypeOptionName = "MsSql")]
public partial class MsSqlDataTypeConverters : DataTypeConverterCollectionBase
{
}

How It Works

  1. Parent Detection: Generator finds TypeCollections that have children referencing them via TypeOption
  2. Child Discovery: Generator finds TypeCollections with TypeOption pointing to parent
  3. Code Generation:
    • Parent: Generates child collection accessor properties (e.g., DataTypeConverters.MsSql)
    • Parent: Generates ChildCollectionTypes property listing all child types
    • Child: Generates normal collection functionality

Generated Code Structure

From TypeCollectionGenerator.cs:516-545:

Parent Collection:

public partial class DataTypeConverters
{
    #region Child Collections

    /// <summary>Gets the MsSql child collection type.</summary>
    public static System.Type MsSql => typeof(MsSqlDataTypeConverters);

    /// <summary>Gets all child collection types.</summary>
    public static IReadOnlyCollection<System.Type> ChildCollectionTypes { get; } = new System.Type[]
    {
        typeof(MsSqlDataTypeConverters),
    };

    #endregion
}

Usage Example

// Access child collection type through parent
var mssqlCollectionType = DataTypeConverters.MsSql;

// Access child collection members directly
var intConverter = MsSqlDataTypeConverters.Int32;

// Enumerate all child collection types
foreach (var childType in DataTypeConverters.ChildCollectionTypes)
{
    Console.WriteLine(childType.Name);
}

Requirements

  1. TypeOption must reference a valid TypeCollection: The parent must have [TypeCollection] attribute
  2. TypeOptionName is required with TypeOption: Both properties must be set together
  3. Child must inherit from parent's base type: Ensures type compatibility

Configuration Options

TypeCollectionAttribute Constructor

From TypeCollectionAttribute.cs:12-57:

// Required parameters: baseType, returnType, collectionType
[TypeCollection(typeof(BaseType), typeof(IReturnType), typeof(MyTypes))]
public partial class MyTypes : TypeCollectionBase<BaseType, IReturnType>
{
}

// With parent-child relationship
[TypeCollection(typeof(ChildBase),
                typeof(IChild),
                typeof(ChildTypes),
                TypeOption = typeof(ParentTypes),
                TypeOptionName = "MyChild")]
public partial class ChildTypes : ParentTypesBase
{
}

// With current-compilation-only discovery
[TypeCollection(typeof(BaseType), typeof(IReturnType), typeof(MyTypes),
                RestrictToCurrentCompilation = true)]
public partial class MyTypes : TypeCollectionBase<BaseType, IReturnType>
{
}

TypeLookupAttribute for Custom Lookup Methods

From TypeLookupAttribute.cs:8-42:

public abstract class MyBaseType : TypeOptionBase<int, MyBaseType>
{
    // TypeOptionBase already provides [TypeLookup("ById")] and [TypeLookup("ByName")]
    // Add custom lookup properties:

    [TypeLookup("ByCategory")]  // Method name is required
    public string Category { get; }

    protected MyBaseType(int id, string name, string category) : base(id, name)
    {
        Category = category;
    }
}

This generates lookup methods using dedicated FrozenDictionary instances:

// Built-in lookups from TypeOptionBase
public static MyBaseType ById(int value) =>
    _byId!.TryGetValue(value, out var result) ? result : NotFound;

public static MyBaseType ByName(string? name) { ... }

// Custom lookup from [TypeLookup("ByCategory")]
public static MyBaseType ByCategory(string value) =>
    _byCategory!.TryGetValue(value, out var result) ? result : NotFound;

Build Configuration


<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
  <DisableSourceGenerators>true</DisableSourceGenerators>
</PropertyGroup>

Troubleshooting

Common Issues

  1. Types Not Discovered

    • Ensure types have [TypeOption(typeof(CollectionType), "Name")] attribute with both parameters
    • Check that types inherit from the correct base type
    • Verify base type name matches [TypeCollection] parameter
  2. Compilation Errors

    • Check generic type constraints match attribute parameters
    • Ensure all referenced types are available at compile time
    • Verify attribute parameters use typeof() correctly
  3. Performance Issues

    • Use dual generic collections for interface return types
    • Prefer FrozenSet<T> over ReadOnlyCollection<T> when possible
    • Consider caching frequently accessed properties

Debug Output

In DEBUG builds, the generator produces debug files:

  • TypeCollectionGenerator.Debug.g.cs: Shows discovery results
  • TypeCollectionGenerator.Init.g.cs: Confirms generator loading

Integration with MSBuild

The source generator integrates seamlessly with MSBuild:

  • Runs during compilation
  • Respects incremental builds
  • Works with IDEs and command line
  • Supports design-time builds for IntelliSense

Package References

For runtime libraries:

<PackageReference Include="FractalDataWorks.Collections.SourceGenerators" Version="*" />

For analyzer-only (abstractions projects where collections are defined):

<PackageReference Include="FractalDataWorks.Collections.SourceGenerators" Version="*">
  <PrivateAssets>all</PrivateAssets>
  <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

Cross-Project Extensibility

The key benefit of TypeCollections is that any project can add new options to collections defined in abstractions:

// In MyCustom.DataExtensions project
[TypeOption(typeof(DataContainerTypes), "Parquet")]
public class ParquetDataContainerType : DataContainerType, IStorageContainer
{
    public ParquetDataContainerType() : base(100, "Parquet", "BigData") { }
}

// Automatically discovered and available
var parquet = DataContainerTypes.Parquet;  // Works immediately

Cross-Assembly Discovery Benefits

  • Plugin Architecture: Core defines contracts, implementations provided by any assembly
  • Extensible Frameworks: Downstream developers can extend without modifying core
  • Modular Development: Teams can add options independently
  • Compile-Time Safety: All options discovered and validated at compile-time

How Embedded Analyzer Pattern Enables Cross-Assembly Discovery

The generator uses an embedded analyzer pattern to enable true cross-assembly extensibility:

1. Abstractions Project Compiles First

<ProjectReference Include="..\Collections.SourceGenerators\..."
                  OutputItemType="Analyzer"
                  ReferenceOutputAssembly="false" />
  • TypeCollection class is defined (DataContainerTypes)
  • Generator runs, discovers 0 TypeOptions (downstream projects haven't compiled yet)
  • Generates collection with infrastructure methods only
  • Generator DLL is embedded in Abstractions.dll via OutputItemType="Analyzer"

2. Implementation Project Compiles

  • References Abstractions.dll (which contains the embedded generator)
  • Generator runs again automatically (triggered by embedded analyzer)
  • Discovers DataContainerTypes from referenced Abstractions.dll
  • Discovers all [TypeOption(typeof(DataContainerTypes), ...)] from current project
  • Generates augmented collection with all TypeOptions from current project

3. Third-Party/Consumer Project Compiles

  • References Abstractions + Implementation packages (both have embedded generators)
  • Generator runs yet again for each referenced assembly with embedded analyzer
  • Discovers TypeCollections from all referenced packages
  • Discovers TypeOptions from:
    • Implementation package DLLs (already compiled)
    • Current project source code (new custom types)
  • Generates collection with built-in + custom options

Key Insight: The generator runs multiple times across project boundaries, each time discovering:

  • TypeCollections from already-compiled referenced assemblies
  • TypeOptions from both referenced assemblies AND current compilation

This multi-pass approach enables true plugin architectures where consumers can add TypeOptions without any modifications to the original packages.

RestrictToCurrentCompilation Flag

For performance-sensitive scenarios where cross-assembly discovery isn't needed:

[TypeCollection(typeof(Base), typeof(IBase), typeof(MyCollection), RestrictToCurrentCompilation = true)]
public partial class MyCollection : TypeCollectionBase<Base, IBase> { }

When true, only TypeOptions in the same compilation are discovered, significantly improving build times for large monorepos.

NotFound Sentinel Generation

The generator creates a NotFound sentinel class for each collection, based on the base type's constructor requirements:

// Generated NotFound sentinel (from TypeCollectionGenerator.cs)
private static readonly StatusBase _notFound = new NotFoundStatuses();
public static StatusBase NotFound => _notFound;

internal sealed class NotFoundStatuses : StatusBase
{
    internal NotFoundStatuses() : base(0, string.Empty) { }
}

The NotFound sentinel provides a null-safe return value for failed lookups.

Testing Source Generators

Unit Tests

From TypeCollectionGeneratedCodeTests.cs:

Tests generator logic with all code in a single compilation:

  • Verifies generator produces correct code
  • Validates diagnostic messages
  • Tests incremental generation behavior
  • Tests parent-child collection relationships

Test Example

From TypeCollectionGeneratedCodeTests.cs:14-54:

[Fact]
public async Task GeneratedCollection_Compiles()
{
    var source = @"
using FractalDataWorks.Collections;
using FractalDataWorks.Collections.Attributes;

namespace Test;

public abstract class ConnectionTypeBase : TypeOptionBase<int, ConnectionTypeBase>
{
    protected ConnectionTypeBase(int id, string name) : base(id, name) { }
}

[TypeCollection(typeof(ConnectionTypeBase), typeof(ConnectionTypeBase), typeof(ConnectionTypes))]
public partial class ConnectionTypes : TypeCollectionBase<ConnectionTypeBase, ConnectionTypeBase>
{
}

[TypeOption(typeof(ConnectionTypes), ""MsSql"")]
public class MsSqlConnectionType : ConnectionTypeBase
{
    public MsSqlConnectionType() : base(1, ""MsSql"") { }
}
";

    var (compilation, diagnostics) = CompilationHelper.RunGenerator(source);

    // No errors
    Assert.Empty(diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
}

Running Tests

# Run all source generator tests
dotnet test --filter "FullyQualifiedName~Collections.SourceGenerators.Tests"

# Run with detailed output
dotnet test --logger "console;verbosity=detailed"

Test Resources

Version Compatibility

  • Target Framework: .NET Standard 2.0+
  • C# Language Version: 9.0+ (for records and init properties)
  • Roslyn Version: 4.0+ (for incremental generators)
There are no supported framework assets in this package.

Learn more about Target Frameworks and .NET Standard.

  • .NETStandard 2.0

    • No dependencies.

NuGet packages (19)

Showing the top 5 NuGet packages that depend on FractalDataWorks.Collections.SourceGenerators:

Package Downloads
CyberdyneDevelopment.Mc3Po.Tools.Abstractions

Tool abstractions for FractalDataWorks Roslyn development tools. Provides TypeCollections for tool categories and parameter types.

CyberdyneDevelopment.Mc3Po.Protocols

Protocol infrastructure for mc3-po - base classes, provider, and ServiceType integration

CyberdyneDevelopment.Mc3Po.Protocols.Plane

Plane protocol implementation for mc3-po - project management integration with Plane

CyberdyneDevelopment.Mc3Po.Protocols.GitHub

Development tools and utilities for the FractalDataWorks ecosystem. Build:

CyberdyneDevelopment.Mc3Po.Protocols.GitLab

GitLab protocol implementation for mc3-po - source control and project management integration

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.6.0-rc.1 53 2/9/2026
Loading failed