Skip to content

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

Closed

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

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