fix(speckl): Rust generator — Date type, record structs, event enum (cycle 73)
Three defects found by deep-inspecting the Rust output against the product spec (FederatedMeetupProduct.speckdl): 1. Date type undefined in Rust — emitted as 'Date' which doesn't exist. Fixed: Date maps to u64 (Unix timestamp) in both the primitive map and the ident fallback path. 2. Interface records (Money, Ticket, Order, etc.) not emitted as Rust structs — only events were emitted. The TS backend has the same gap. Fixed: record-kind interfaces now emit as Rust structs. 3. Decision log typed as Vec<FirstEventName> instead of an enum union. Fixed: emit a SpeckName_Event enum with one variant per event type, type the decision log as Vec<SpeckName_Event>. 57/57 tests pass. Both specs generate clean Rust output.
This commit is contained in:
parent
e5ee3fe1c0
commit
84b8962d22
1 changed files with 34 additions and 3 deletions
|
|
@ -198,6 +198,29 @@ function emitSpeck(speck: SpeckNode): string {
|
|||
return `#[derive(Debug, Clone)]\npub struct ${e.name} {\n${fields}\n}`;
|
||||
}).join('\n\n');
|
||||
|
||||
// Cycle 73: Event enum union for the decision log.
|
||||
// Each variant wraps the corresponding event struct.
|
||||
const eventEnum = events.length > 0
|
||||
? `#[derive(Debug, Clone)]\npub enum ${speck.name}Event {\n` +
|
||||
events.map(e => ` ${e.name}(${e.name}),`).join('\n') +
|
||||
`\n}`
|
||||
: '';
|
||||
|
||||
// Cycle 73: Interface record structs (Money, Ticket, Order, etc.)
|
||||
// Previously only events were emitted as structs. Interface records
|
||||
// with fields (kind === 'record') are now emitted too.
|
||||
const recordInterfaces = allInterfaces.filter(iface =>
|
||||
iface?.kind === 'record' || (iface?.fields && iface.fields.length > 0)
|
||||
);
|
||||
const recordStructs = recordInterfaces.map(iface => {
|
||||
const fields = iface.fields.map((f: any) => {
|
||||
const name = snakeCase(cleanName(f.name));
|
||||
const ty = rustType(f.type, speck.name, enumMap);
|
||||
return ` pub ${name}: ${ty},`;
|
||||
}).join('\n');
|
||||
return `#[derive(Debug, Clone)]\npub struct ${iface.name} {\n${fields}\n}`;
|
||||
}).join('\n\n');
|
||||
|
||||
const structName = `${speck.name}Machine`;
|
||||
|
||||
// State variables with original name + snake_case name mapping
|
||||
|
|
@ -276,8 +299,11 @@ function emitSpeck(speck: SpeckNode): string {
|
|||
` }`
|
||||
).join('\n\n');
|
||||
|
||||
// Decision log type
|
||||
const logEventType = events.length > 0 ? `Vec<${events[0].name}>` : 'Vec<String>';
|
||||
// Decision log type — cycle 73: use an enum union of all event types,
|
||||
// not just the first one. If there are no events, fall back to String.
|
||||
const logEventType = events.length > 0
|
||||
? `Vec<${speck.name}Event>`
|
||||
: 'Vec<String>';
|
||||
|
||||
// Struct definition
|
||||
const structDef = `/// Speck state machine: ${speck.name}\n` +
|
||||
|
|
@ -318,11 +344,13 @@ function emitSpeck(speck: SpeckNode): string {
|
|||
`// DO NOT EDIT MANUALLY — regenerate from .speckdl source\n\n` +
|
||||
`use std::collections::{HashMap, HashSet};\n\n` +
|
||||
`${stateEnum}\n\n` +
|
||||
`${recordStructs ? recordStructs + '\n\n' : ''}` +
|
||||
`${eventStructs}\n\n` +
|
||||
`${eventEnum ? eventEnum + '\n\n' : ''}` +
|
||||
`${structDef}\n\n` +
|
||||
`${constructor}\n\n` +
|
||||
`${actionImpl}\n` +
|
||||
`${invariantImpl}\n` +
|
||||
`${invariantImpl}` +
|
||||
`${verifyImpl}\n`;
|
||||
|
||||
return file;
|
||||
|
|
@ -619,6 +647,8 @@ function rustType(t: any, speckName: string = '', enumMap?: Map<string, string[]
|
|||
Nat: 'u64', Int: 'i64', Real: 'f64',
|
||||
Bool: 'bool', String: 'String', Bytes: 'Vec<u8>',
|
||||
Number: 'f64',
|
||||
// Cycle 73: Date → u64 Unix timestamp (avoids chrono dependency).
|
||||
Date: 'u64',
|
||||
};
|
||||
return map[t.name] || t.name.toLowerCase();
|
||||
}
|
||||
|
|
@ -645,6 +675,7 @@ function rustType(t: any, speckName: string = '', enumMap?: Map<string, string[]
|
|||
if (rawName === 'Int') return 'i64';
|
||||
if (rawName === 'Bool') return 'bool';
|
||||
if (rawName === 'String') return 'String';
|
||||
if (rawName === 'Date') return 'u64';
|
||||
// Handle Option<T> from SpeckDL's Option(Type) syntax
|
||||
const optionMatch = rawName.match(/^Option[_(]\s*(\w+)\s*\)?$/);
|
||||
if (optionMatch) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue