clockworklabs/SpacetimeDB · error · InvalidOperationException

Expected an interpolated string as a lambda body but got

Error message

Expected an interpolated string as a lambda body but got {interpolate.Body}.

What it means

This is internal to the SpacetimeDB C# source generator, not user module code. ErrorDescriptor parses its lambda as an expression tree and expects the body to be the compiler-generated FormattableStringFactory.Create(format, args) call that an interpolated string lowers to. If the body is anything else — a plain string, a concatenation, an arbitrary method call — the constructor throws InvalidOperationException with the offending expression.

Solutions

  1. Make the lambda body exactly one interpolated string: ctx => $"Unsupported type {ctx.Symbol}"
  2. Do not pre-format the message outside the lambda or use concatenation inside it
  3. Keep every dynamic value as an interpolation hole so it lowers into the FormattableStringFactory args array
  4. If you hit this as an SDK end user, your module code did not cause it — report it as a generator bug

Example fix

// before
new ErrorDescriptor<TypeContext>(group, "UnsupportedType",
    ctx => "Unsupported type " + ctx.Symbol, ...) // throws

// after
new ErrorDescriptor<TypeContext>(group, "UnsupportedType",
    ctx => $"Unsupported type {ctx.Symbol}", ...) // lowers to FormattableStringFactory.Create
Defensive patterns

Strategy: try-catch

Validate before calling

// generator-internal smoke test: every descriptor must build
foreach (var d in AllDescriptors) {
    _ = d.Descriptor; // constructing via the lambda should not throw
}

Try / catch

try {
    var descriptor = new ErrorDescriptor<TContext>(group, title, ctx => $"...", toLocation);
} catch (InvalidOperationException e) {
    throw new Xunit.Sdk.XunitException($"Bad ErrorDescriptor lambda: {e.Message}");
}

Prevention

When it happens

Trigger: Inside the BSATN.Codegen codebase: constructing new ErrorDescriptor<T>(group, title, ctx => <body that is not a single interpolated string>, toLocation). Triggers include returning a literal string, using `+` concatenation, pre-formatting outside the lambda, or hand-calling FormattableStringFactory.Create with a different argument shape.

Common situations: Contributors adding generator diagnostics who write ctx => "literal" instead of ctx => $"literal"; refactors that move formatting out of the lambda; rare Roslyn upgrades that change expression-tree lowering.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20). Data as JSON: /api/errors/24c3ff45b4f1a101. Report an issue: GitHub.

Appendix: source

Thrown at crates/bindings-csharp/BSATN.Codegen/Diag.cs:53

        this.toLocation = toLocation;
        if (
            interpolate.Body
                is not MethodCallExpression
                {
                    Method:
                    {
                        DeclaringType: var declaringType,
                        Name: nameof(FormattableStringFactory.Create)
                    },
                    Arguments: [
                        ConstantExpression { Value: string messageFormat },
                        NewArrayExpression args,
                    ]
                }
            || declaringType != typeof(FormattableStringFactory)
        )
        {
            throw new InvalidOperationException(
                $"Expected an interpolated string as a lambda body but got {interpolate.Body}."
            );
        }
        descriptor = new(
            id: group.NextId(),
            title: title,
            messageFormat: messageFormat,
            category: group.Name,
            defaultSeverity: DiagnosticSeverity.Error,
            isEnabledByDefault: true
        );
        makeFormatArgs = Expression
            .Lambda<Func<TContext, object[]>>(args, interpolate.Parameters)
            .Compile();
    }

    public ErrorDescriptor(
        ErrorDescriptorGroup group,

View on GitHub (pinned to 6dee26c6ef)