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
- Make the lambda body exactly one interpolated string: ctx => $"Unsupported type {ctx.Symbol}"
- Do not pre-format the message outside the lambda or use concatenation inside it
- Keep every dynamic value as an interpolation hole so it lowers into the FormattableStringFactory args array
- 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
- Every new ErrorDescriptor gets a unit test asserting the constructor succeeds
- Keep lambda bodies as single interpolated strings
- Run the generator test suite before releasing SDK changes
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
- Could not resolve type
- Enum does not have a [SpacetimeDB.Type] attribute
- InvalidTableVisibility
- Unsupported special type
- Unsupported system type
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)