Parser drops field declarations in record-style interface blocks (only enum variants are captured) #61
Labels
No labels
blocked
bug
design
docs
engineering
feature
infra
priority:high
priority:low
priority:medium
question
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: sscoble/speckl#61
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The SpeckDL parser at
compiler/src/parser.tsaround line 821 silently drops field declarations insideinterfaceblocks that use thename: Typeform (record-style interfaces). Only themethod(params) returns Typeform is captured intoiface.methods[]. This makes it impossible for any generator (rust, typescript, protobuf) to emit the fields of record-style interfaces.Reproduction
Observed
The 4 record-style interfaces (BranchInfo, CustodyDeclaration, EquivocationEvidence, EventRecord) all have 5 fields in the source but 0 captured in the AST. Only the enum-style interface (CustodyTier with variants) parses correctly.
Impact
Any generator that wants to emit record-style interface fields fails silently. Concrete effects:
generators/rust.tsemits emptypub struct CustodyDeclaration {}for record-style interfaces (no fields visible)generators/typescript-state-machine.tsemitsinterface CustodyDeclaration {}(empty)generators/protobuf.ts(new, in PR) emitsenum CustodyDeclaration { CustodyDeclaration_UNSPECIFIED = 0; }— the generator can't tell the difference between a record-style and enum-style interfaceThis is the load-bearing bug that blocks federated-meetup from compiling its record-style types (
BranchInfo,CustodyDeclaration,EquivocationEvidence,EventRecord).Proposed fix
In
compiler/src/parser.tsaround line 821 (theinterfaceblock parser), detect two forms inside the block:Variant form:
VariantName(one identifier per line) → continue producingmethods: [{ name: 'VariantName', params: [], returnType: { type: 'ident', name: 'Void' } }]Field form:
field_name: TypeName(one per line) → producefields: [{ name: 'field_name', type: parseTypeExpr('TypeName') }]The block's kind should be determined by the first non-blank, non-comment line:
name: Type(lowercase start) → fieldname(params) returns Type→ method (existing)Once both forms are captured, the AST node becomes a discriminated union:
InterfaceNode { kind: 'enum' | 'record' | 'service', variants?, fields?, methods? }.Acceptance criteria
BranchInfoand friends inFederatedMeetup.speckdlshowfields.length === 5after parsingCustodyTierstill showsmethods.length === 5(variants)generators/rust.tsemits non-empty structs for record interfacesgenerators/protobuf.tsemitsmessage BranchInfo { ... }instead of empty enumPriority
High — without this, the protobuf backend (and probably others) emit broken output for any spec with record-style interfaces, which is the common case.
References
compiler/src/parser.ts:771-825— interface block parser (rough line range, exact location needs verification)compiler/src/parser.ts:1110— also has afieldscollection site, may be relatedFixed and verified. The protobuf backend now compiles record-style interfaces correctly:
Compiles to:
Changes:
compiler/src/parser.ts—InterfaceNodenow has akind: 'enum' | 'record' | 'service' | 'mixed'discriminator.parseInterfaceBlockMultilinerecognizes thename: Typeform and pushes into a newfields: InterfaceField[]array. The kind is inferred from the contents: lowercase-startingname: Type→ record; bareIdentifier→ enum variant;name(params) -> Type→ service method.compiler/src/generators/protobuf.ts— uses the parser'skindto route each interface to the right proto3 construct (enum vs message vs skip).parseMemberBlockupdated to includeservice(so the newservice { rpc X(R) returns (R); }block dispatches correctly).End-to-end dogfood:
FederatedMeetup.speckdl(188 lines, includes 5 record interfaces, 1 enum interface, 14 events, 1 service with 5 RPCs) compiles to a 188-line.proto.buf generatewithprotoc-gen-go+protoc-gen-connect-go+protoc-gen-validateproduces working Go code + ConnectRPC handlers.CustodyTierenum.import pb "fmspeckl/gen"is the only line the user writes.Local commits on
main:69f668d feat(speckl): protobuf backend + service block + record interface fields.