Skip to content

Unknown TypeKind or AccessorKind writes half a declaration before throwing, and an Expression accessor with no BodyFactory emits get => ; #142

Open

Description

@matt-edmondson

What's wrong

#128 (fixed by #140) made two paths validate before writing: a property with no accessors, and an unknown OperatorKind. Three related paths are still unfixed. Two of them, which #128 itself named, write output first and throw afterwards. The third emits code that does not compile and raises no error.

  1. Unknown ClassTemplate.Kind. ClassTemplate.WriteTo (CodeBlocker/Templates/ClassTemplate.cs:67-69) calls base.WriteTo first, which writes the doc comment, attributes and modifiers. Only after that does it call TypeKindKeywords.For(Kind), which throws ArgumentOutOfRangeException. For a nested class, the parent's using Scope then closes onto the unfinished line.
  2. Unknown AccessorTemplate.Kind. PropertyTemplate.WriteTo writes Type Name, a newline and {, then indents. Only then does AccessorTemplate.WriteTo reach default: throw new InvalidOperationException("Unknown accessor kind ...") (AccessorTemplate.cs:107).
  3. Expression accessor with a null body. new AccessorTemplate { Kind = AccessorKind.Expression } leaves BodyFactory == null, and object-initializer syntax allows it. AccessorTemplate.cs:92-95 passes that null to TemplateRendering.WriteExpressionBody, which writes get => ;. The output does not compile and nothing reports it.

Reproduction (main @ f86da28)

var outer = new ClassTemplate { Name = "Outer" };
outer.NestedClasses.Add(new ClassTemplate { Name = "Inner", Kind = (TypeKind)99, Keywords = { "public" } });
cb.AddClass(outer); // throws ArgumentOutOfRangeException; buffer: "class Outer\n{\n\tpublic }\n"

var c = new ClassTemplate { Name = "C" };
c.Members.Add(new FieldTemplate { Type = "int", Name = "a" });
c.Members.Add(new PropertyTemplate { Type = "int", Name = "P", Keywords = { "public" },
    Getter = new AccessorTemplate { Kind = (AccessorKind)99 } });
cb.AddClass(c); // throws; buffer ends "\tpublic int P\n\t{\n\t}\n"

var d = new ClassTemplate { Name = "C" };
d.Members.Add(new PropertyTemplate { Type = "int", Name = "P",
    Getter = new AccessorTemplate { Kind = AccessorKind.Expression } });
cb.AddClass(d); // no exception; emits "\t\tget => ;"

Why it matters

A source generator might catch the exception and keep using the same CodeBlocker. It then emits corrupt source, the exact failure #128 set out to prevent. Cases 1 and 2 need an out-of-range enum cast to trigger. Case 3 needs only ordinary object-initializer code, and the generator ships C# that fails to compile with no diagnostic.

Suggested fix / acceptance criteria

Activity

  1. matt-edmondson commented on Oct 18, 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

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions