{"record":{"id":"24c3ff45b4f1a101","repo":"clockworklabs/SpacetimeDB","slug":"expected-an-interpolated-string-as-a-lambda-body-b","errorCode":null,"errorMessage":"Expected an interpolated string as a lambda body but got {interpolate.Body}.","messagePattern":"Expected an interpolated string as a lambda body but got (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"crates/bindings-csharp/BSATN.Codegen/Diag.cs","lineNumber":53,"sourceCode":"        this.toLocation = toLocation;\n        if (\n            interpolate.Body\n                is not MethodCallExpression\n                {\n                    Method:\n                    {\n                        DeclaringType: var declaringType,\n                        Name: nameof(FormattableStringFactory.Create)\n                    },\n                    Arguments: [\n                        ConstantExpression { Value: string messageFormat },\n                        NewArrayExpression args,\n                    ]\n                }\n            || declaringType != typeof(FormattableStringFactory)\n        )\n        {\n            throw new InvalidOperationException(\n                $\"Expected an interpolated string as a lambda body but got {interpolate.Body}.\"\n            );\n        }\n        descriptor = new(\n            id: group.NextId(),\n            title: title,\n            messageFormat: messageFormat,\n            category: group.Name,\n            defaultSeverity: DiagnosticSeverity.Error,\n            isEnabledByDefault: true\n        );\n        makeFormatArgs = Expression\n            .Lambda<Func<TContext, object[]>>(args, interpolate.Parameters)\n            .Compile();\n    }\n\n    public ErrorDescriptor(\n        ErrorDescriptorGroup group,","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/6dee26c6efc2856793e12b148a59742964f5d783/crates/bindings-csharp/BSATN.Codegen/Diag.cs#L35-L71","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nnew ErrorDescriptor<TypeContext>(group, \"UnsupportedType\",\n    ctx => \"Unsupported type \" + ctx.Symbol, ...) // throws\n\n// after\nnew ErrorDescriptor<TypeContext>(group, \"UnsupportedType\",\n    ctx => $\"Unsupported type {ctx.Symbol}\", ...) // lowers to FormattableStringFactory.Create","handlingStrategy":"try-catch","validationCode":"// generator-internal smoke test: every descriptor must build\nforeach (var d in AllDescriptors) {\n    _ = d.Descriptor; // constructing via the lambda should not throw\n}","typeGuard":null,"tryCatchPattern":"try {\n    var descriptor = new ErrorDescriptor<TContext>(group, title, ctx => $\"...\", toLocation);\n} catch (InvalidOperationException e) {\n    throw new Xunit.Sdk.XunitException($\"Bad ErrorDescriptor lambda: {e.Message}\");\n}","preventionTips":["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"],"tags":["csharp","source-generator","roslyn","expression-trees","internal"],"backgroundTag":"expression-tree-shape-mismatch","analyzedSha":"6dee26c6efc2856793e12b148a59742964f5d783","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}