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 class with an empty expression-bodied property is still written with its closing brace, minus that member and everything after it (#164 again, since #178) #183
varcls=newClassTemplate{Name="C",Members={newFieldTemplate{Type="int",Name="f"},newPropertyTemplate{Type="int",Name="P",ExpressionBodyFactory= b =>{}},},};cls.WriteTo(cb);
WriteTo throws InvalidOperationException ("…has an expression body whose callback wrote no expression…"). By then the buffer already holds:
classC{intf;}
That is a well-formed class that silently lacks P and every member and nested class after it. It is the situation #164 was about, which #169 fixed by validating every member before writing the class header.
Reproduced against main @ aa7ca53 on net10.0. The same thing happens one level up through SourceFileTemplate / AddClasses, and for accessor expressions (Getter = AccessorTemplate.Expression(b => { })).
Why
#178 added the empty-expression check in TemplateRendering.RenderExpression (CodeBlocker/Templates/TemplateRendering.cs:338-349). That runs from PropertyTemplate.WriteTo and from the accessor write path, not from Validate(). So ClassTemplate.Validate() (ClassTemplate.cs:105-118) passes. The member then throws inside WriteBodyTo's using Scope, and disposing that scope writes the closing }.
Why it matters
A generator that catches the exception and reports it gets a compilable file with members missing. One that writes the buffer anyway (for example, a source generator that adds whatever was produced) ships a type with a silently truncated API. #169 exists to prevent exactly this.
Suggested fix
Make the check part of validation. Either:
have PropertyTemplate.Validate() and AccessorTemplate.Validate() render the expression and throw when it is empty, or
render all member fragments before the class header is written.
Acceptance criteria
In the repro, WriteTo throws and nothing is written to the CodeBlocker.
Related: #176 is the same "half-written then closed" shape when a user BodyFactory throws. This issue is about a check the library itself makes, which belongs in Validate().
Priority: High. The output is silently wrong: a well-formed class that is missing a member and everything after it. A generator that writes the buffer anyway ships a truncated API without knowing it, which is the outcome Check a class's members before writing any of it [patch] #169 was meant to prevent.
Area:TemplateRendering.RenderExpression and validation in PropertyTemplate, AccessorTemplate and ClassTemplate.Validate()
Activity
matt-edmondson commentedon Nov 5, 2026
Triage
TemplateRendering.RenderExpressionand validation inPropertyTemplate,AccessorTemplateandClassTemplate.Validate()Validate()would also give An expression body that writes only a comment or directives still rendersint P => ;// TODO(CS1525): the #175 guard checks only for blank lines #184's comment-only check a home there.int P => ;// TODO(CS1525): the #175 guard checks only for blank lines #184 (the same guard, with comment-only bodies), and A BodyFactory that throws leaves a half-written member, and inside a class the closing brace lands on it:public void M()}#176 (the same half-written-then-closed shape when a userBodyFactorythrows). Fixing A class with an empty expression-bodied property is still written with its closing brace, minus that member and everything after it (#164 again, since #178) #183 and An expression body that writes only a comment or directives still rendersint P => ;// TODO(CS1525): the #175 guard checks only for blank lines #184 together makes sense. No open PR covers it. Drop the blank lines around a braced member body [patch] #182 touchesTemplateRendering.WriteBody, so expect a nearby merge conflict.Generated by Claude Code