Skip to main content
Railway-oriented programming for source generator pipelines. Never lose a diagnostic.

The Problem

Traditional generator code loses diagnostics:
// BAD: Diagnostics get lost
var model = ExtractModel(syntax);
if (model == null) return; // Where's the diagnostic?

var validated = Validate(model);
if (!validated.Success) return; // Lost again!

The Solution

DiagnosticFlow<T> carries both value AND diagnostics through the pipeline:
// GOOD: Diagnostics flow through
symbol.ToFlow(nullDiag)
    .Then(ExtractModel)
    .Then(Validate)
    .Then(Generate)
    .ReportAndContinue(context);

Creating Flows

// From value
var flow = DiagnosticFlow.Ok(value);

// From nullable (fails if null)
var flow = symbol.ToFlow(nullDiag);

// With initial diagnostics
var flow = DiagnosticFlow.Ok(value, warnings);

// Failed flow
var flow = DiagnosticFlow.Fail<T>(errorDiag);

Chaining Operations

Then - Transform Value

flow.Then(value => TransformValue(value))
    .Then(transformed => AnotherTransform(transformed));

Select - Map Without Flow

flow.Select(value => value.Name)
    .Select(name => name.ToUpperInvariant());

Where - Filter with Diagnostic

flow.Where(
    predicate: m => m.IsAsync,
    onFail: asyncRequiredDiag
);

WarnIf - Conditional Warning

flow.WarnIf(
    predicate: m => m.IsObsolete,
    warning: obsoleteWarning
);

Combining Flows

// Tuple of two flows (both must succeed)
var combined = DiagnosticFlow.Zip(flow1, flow2);
// Result: DiagnosticFlow<(T1, T2)>

// Collect all (all must succeed, diagnostics accumulated)
var all = DiagnosticFlow.Collect(flows);
// Result: DiagnosticFlow<ImmutableArray<T>>

Result Handling

// Get value or default
var value = flow.ValueOrDefault(fallback);

// Pattern match
flow.Match(
    onSuccess: value => HandleSuccess(value),
    onFailure: diagnostics => HandleFailure(diagnostics)
);

// Execute side effect
flow.Do(value => LogValue(value));

Pipeline Integration

Use with IncrementalValuesProvider:
var pipeline = context.SyntaxProvider
    .ForAttributeWithMetadataName("MyAttribute", ...)
    .SelectFlow(ctx => ExtractModel(ctx))
    .ThenFlow(model => Validate(model))
    .WarnIf(model => model.IsDeprecated, deprecatedWarn)
    .ReportAndContinue(context)
    .Select(model => GenerateCode(model));

context.RegisterSourceOutput(pipeline, (ctx, code) =>
    ctx.AddSource(code.Name, code.Content));

Properties

PropertyDescription
IsSuccessTrue if no errors
IsFailedTrue if has errors
HasErrorsTrue if any error-severity diagnostic
ValueThe wrapped value (throws if failed)
DiagnosticsAll accumulated diagnostics