fix(speckl): strip default-value syntax from event field types (cycle 65)

End-to-end compile of FederatedMeetup.speckdl (with the cycle-65
Transition envelope added) surfaced a real bug: the speckdl uses
default-value syntax on event fields:

  initial_mesh_peers: List<InitialMeshPeer> = []

The default is a Speckl semantic — a state-machine init value —
but the .proto wire format has no place for it. Without stripping,
the type expression 'List<InitialMeshPeer> = []' was emitted
verbatim as the field type, producing broken proto3 syntax that
protoc rejected.

Fix: parseEventBlockMultiline now strips '= <expr>' suffix from
the type expression before calling parseTypeExpr. The default value
is dropped (it's a Speckl semantic only; the wire format carries
no default).

Tests: 1 new parser test covers the federated-meetup
'initial_mesh_peers: List<InitialMeshPeer> = []' shape. All 56
tests pass (55 baseline + 1 new). tsc build clean.

End-to-end verified: with this fix and the cycle-65 federated-
meetup speckdl changes, the Speckl-generated .proto now compiles
cleanly with 'protoc' (previously rejected).
This commit is contained in:
Scott Scoble 2026-06-28 22:36:36 -07:00
parent f37c0ccc57
commit 7beaf6f3cc
2 changed files with 41 additions and 1 deletions

View file

@ -853,4 +853,35 @@ speck Second {
}
});
});
// Cycle 65: default-value syntax `= <expr>` on event fields was
// bleeding into the type expression. Real federated-meetup has
// initial_mesh_peers: List<InitialMeshPeer> = []
// emitted as `List<InitialMeshPeer> = [] initial_mesh_peers = 1;`
// which protoc rejects. The fix strips `= <expr>` from the type
// before parsing (the default is a Speckl semantic; the .proto wire
// format has no place for it).
describe('default value stripping (cycle 65)', () => {
it('should strip `= []` from event field type expressions', () => {
const content = `speck One {
event CreateGroup {
initial_mesh_peers: List<InitialMeshPeer> = []
}
}`;
const ast = parseSpeckContent(content);
const ev = ast.specks[0].members.find(m => m.name === 'CreateGroup');
if (ev?.type === 'event') {
const f = ev.fields[0];
expect(f.name).toBe('initial_mesh_peers');
expect(f.type.type).toBe('list');
expect(f.type.elementType?.name).toBe('InitialMeshPeer');
// The full type string should not contain '= []' anywhere — verify
// via the elementType name and the parent's name (both undefined
// for List, so we check the parser did not preserve the default
// in any visible field).
const asAny = f.type as any;
expect(JSON.stringify(asAny)).not.toContain('= []');
}
});
});
});

View file

@ -1309,7 +1309,16 @@ function parseEventBlockMultiline(lines: string[], startIndex: number, startBrac
// bytes // 4 bytes (IPv4 overlay) mesh_ip = 2;
// (broken syntax — comment is in the type position).
const noComment = line.replace(/\/\/.*$/, '').trim();
const match = noComment.match(/^(\w+)\s*:\s*(.+?),?$/);
// Cycle 65: strip default-value suffix `= <expr>` from the type
// expression. The federated-meetup speckdl has fields like
// initial_mesh_peers: List<InitialMeshPeer> = []
// where the default is meaningful in Speckl semantics (a state-
// machine init) but the .proto wire format doesn't carry a
// default — it's a generator concern. Without this, the type
// expression was emitted as `List<InitialMeshPeer> = []` and
// protoc rejects the resulting syntax.
const noDefault = noComment.replace(/\s*=\s*[^,]+$/, '').trim();
const match = noDefault.match(/^(\w+)\s*:\s*(.+?),?$/);
if (match) {
fields.push({ name: match[1], type: parseTypeExpr(match[2].replace(/,$/, '').trim()) });
}