Skip to content

A multi-line expression body from a Method/Constructor/Operator BodyFactory drops its => to the next line, unlike PropertyTemplate.ExpressionBodyFactory #163

Open

Description

@matt-edmondson

What's wrong

TemplateRendering.WriteBody (CodeBlocker/Templates/TemplateRendering.cs) decides a body's shape by counting its non-blank lines:

  • A single line is treated as an expression body and kept on the declaration line.
  • Two or more lines are treated as a braced block. The declaration line is ended and the fragment is spliced in below it.

A BodyFactory that writes a multi-line expression body, one starting with =>, therefore takes the braced-block path, and the arrow ends up on its own line at the declaration's indent:

public int M()
=> k switch
{
    _ => 0,
};

Through PropertyTemplate.ExpressionBodyFactory (which uses WriteExpressionBody and was fixed in #112/#114), the same expression renders as:

public int X => k switch
{
    _ => 0,
};

MethodTemplate, ConstructorTemplate and OperatorTemplate all go through WriteBody. Their docs say the callback "supplies its own braces, or writes an expression body such as => value;", but only the single-line form of an expression body renders as documented.

Reproduction

Reproduced at 21ac04c. The output was compiled with Roslyn: no errors, so this is a layout defect, not a compile break.

var c = new ClassTemplate { Name = "C", Keywords = { "public" } };
c.Members.Add(new MethodTemplate
{
    Type = "int", Name = "M", Keywords = { "public" },
    BodyFactory = cb =>
    {
        cb.WriteLine("=> k switch");
        using (new ScopeWithTrailingSemicolon(cb)) { cb.WriteLine("_ => 0,"); }
    },
});
cb.AddClass(c);
// actual:   "\tpublic int M()\n\t=> k switch\n\t{\n\t\t_ => 0,\n\t};\n"
// expected: "\tpublic int M() => k switch\n\t{\n\t\t_ => 0,\n\t};\n"

An explicit operator with the same body shows the same layout (public static explicit operator int(C c) / => c.k switch / …).

Why it matters

The output compiles, but it doesn't match the property form. That breaks golden-file comparisons, and multi-line switch, collection-expression and initialiser bodies on methods and operators look malformed in generated code.

Suggested fix and acceptance criteria

  • In WriteBody, when the first non-blank line starts with => (after trimming leading whitespace), write " " + firstLine on the declaration line and splice the remaining lines at the current indent, as WriteExpressionBody does for continuation lines. Leading blank lines are skipped, as they already are.
  • Braced bodies (first line {) keep their current layout.
  • Constructors with an initialiser (: this(...) on its own indented line) keep the arrow on the initialiser line.
  • Tests: a nested method, a constructor and an operator with a multi-line => body produce the same layout as a PropertyTemplate.ExpressionBodyFactory with the same expression.

Open PR #161 also edits WriteBody (comment-only bodies), so the two changes will need coordinating.

Activity

  1. matt-edmondson commented on Nov 2, 2026

    @matt-edmondson
    ContributorAuthor

    Triage


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions