You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A BodyFactory that throws leaves a half-written member, and inside a class the closing brace lands on it: public void M()} #176
Method, constructor, operator and property templates write their doc comment, attributes and signature first. Only then do they call the user's BodyFactory/ExpressionBodyFactory, through TemplateRendering.RenderFragment (WriteBody at TemplateRendering.cs#L281, WriteExpressionBody at #L328). If the callback throws, the header is already in the buffer.
Inside a class, the using Scope in ClassTemplate.cs#L129 still writes } while the exception unwinds, and that brace lands on the unfinished line.
The exception propagates, and the buffer then holds:
class C
{
int a;
int b;
public void M()}
A standalone property with a throwing ExpressionBodyFactory and a summary leaves /// <summary>s</summary> followed by int P.
Why it matters
The callback is user code: a generator walking its model can throw on a malformed input. The templates promise elsewhere that when they throw, "nothing is written" and a caller that catches the exception can keep using the CodeBlocker. Here the caller is left with a buffer they can't recover. If they log the error and emit the rest of the file (common in source generators that report a diagnostic and continue), the file doesn't compile.
This is related to #164 and its open PR #169, but it's a different case. Those pre-check things the library itself rejects, before anything is written. Validate() never runs user callbacks, so a throwing callback is still unhandled after #169.
Suggested fix / acceptance criteria
The callbacks already render into a separate buffer (RenderFragment). Render the body, expression body and accessor bodies before writing the member header, then write the header and splice in the pre-rendered text.
For ClassTemplate, render the members into a fragment before writing the declaration and opening the Scope, so a member failure leaves nothing of the class behind.
Tests: in both repros above the exception propagates and cb.ToString() is unchanged from before the call.
Category: Bug Priority: Medium. The output is corrupted, but only on the exception path. A caller only hits it if they catch an exception thrown by their own BodyFactory/ExpressionBodyFactory and then keep writing to the same CodeBlocker. That is a real pattern in source generators, and it breaks the library's "nothing is written when a template throws" promise. Normal successful generation is unaffected. Area / suggested assignment: Templates rendering pipeline (TemplateRendering.WriteBody / WriteExpressionBody, ClassTemplate.WriteTo). This is the maintainer of the recent "check before writing" series.
The prerequisites from the earlier triage have landed: #169 and #178 are both merged. This is still not actionable yet, because open PR #182 ("Drop the blank lines around a braced member body") edits TemplateRendering.cs, the same body-writing path this fix has to restructure. The two would conflict.
Activity
matt-edmondson commentedon Nov 3, 2026
Triage
Category: Bug
Priority: Medium. The output is corrupted, but only on the exception path. A caller only hits it if they catch an exception thrown by their own
BodyFactory/ExpressionBodyFactoryand then keep writing to the sameCodeBlocker. That is a real pattern in source generators, and it breaks the library's "nothing is written when a template throws" promise. Normal successful generation is unaffected.Area / suggested assignment: Templates rendering pipeline (
TemplateRendering.WriteBody/WriteExpressionBody,ClassTemplate.WriteTo). This is the maintainer of the recent "check before writing" series.Duplicates / in-progress check
Validate()never runs callbacks.ClassTemplate.WriteTo, so they will conflict. Land Check a class's members before writing any of it [patch] #169 first, then render members into a fragment on top of it.int P => ;/get => ;(CS1525), the output Validate says it prevents #175) addsTemplateRendering.RenderExpression, which pre-renders the expression body before writing. That is most of what this issue needs for the expression-body case. Build on Refuse an expression body that writes no expression, before writing [patch] #178 rather than in parallel with it.get => ;#142 (closed) dealt with the same "half a declaration before throwing" family for unknown kinds.Next steps
Scope.cb.ToString()is unchanged after the exception.Generated by Claude Code
matt-edmondson commentedon Nov 4, 2026
Triage
The prerequisites from the earlier triage have landed: #169 and #178 are both merged. This is still not actionable yet, because open PR #182 ("Drop the blank lines around a braced member body") edits
TemplateRendering.cs, the same body-writing path this fix has to restructure. The two would conflict.Scope.TemplateRendering.Generated by Claude Code