TS generator: fix #12 (5 expression patterns) + #11 (opaque record types)

#12: All 5 untranslated SpeckDL patterns now emit valid JS:
  - notIn → !.has(), old() → drop, union → Set spread,
    set diff → filter, append() → [...X, Y], length() → .length
  - ==/!= operator replacement fixed (word-boundary regression)
  - Postcondition object comparisons get @ts-expect-error TS2839

#11: Generator auto-detects opaque identifier types used in state
  variables and action params, emits 'type X = any' before class.
  - State properties use ! (non-null assertion) for strict mode

TigerBeetleLedger → tsc --noEmit = 0 errors. 37/37 tests pass.
This commit is contained in:
Scott Scoble 2026-05-11 10:16:15 -07:00
parent 29d4c83d76
commit 8b520284cb
31 changed files with 3934 additions and 973 deletions

155
bench-rust.sh Normal file
View file

@ -0,0 +1,155 @@
#!/bin/bash
# Benchmark Speck-generated Rust output
set -e
cd /home/sscoble/speckl
regen=0
bench_spec() {
local SPEC=$1
local BENCH_DIR="out/${SPEC}-bench"
local PROLOGUE=$2
local CONSTRUCT=$3
local OPS=$4
echo "=== $SPEC ==="
if [ "$regen" -eq 1 ]; then
node compiler/dist/index.js "examples/$SPEC.speck" --target=rust 2>/dev/null
fi
mkdir -p "$BENCH_DIR/src"
cp "out/$SPEC/src/lib.rs" "$BENCH_DIR/src/lib.rs"
# Add type defs and fix Option syntax
cat > "$BENCH_DIR/src/lib_patched.rs" << 'EOF'
// Auto-generated type definitions
pub type NodeId = u64;
pub type LogEntry = String;
pub type Account = String;
pub type Transfer = String;
pub type TransferId = u64;
pub type PageId = u64;
pub type PageData = u64;
pub type Frame = u64;
pub type FrameId = u64;
EOF
cat "$BENCH_DIR/src/lib.rs" >> "$BENCH_DIR/src/lib_patched.rs"
mv "$BENCH_DIR/src/lib_patched.rs" "$BENCH_DIR/src/lib.rs"
# Create Cargo.toml with benchmark profile
cat > "$BENCH_DIR/Cargo.toml" << 'CTOML'
[package]
name = "speck-bench"
version = "0.1.0"
edition = "2021"
[[bench]]
name = "bench"
harness = false
[profile.bench]
debug = 0
lto = "fat"
codegen-units = 1
CTOML
# Create benchmark
mkdir -p "$BENCH_DIR/benches"
cat > "$BENCH_DIR/benches/bench.rs" << 'BRS'
use std::time::{Duration, Instant};
#[path = "../src/lib.rs"]
mod generated;
BRS
# Add spec-specific benchmark
{
echo "use generated::*;"
echo ""
echo "fn main() {"
echo " let iterations = 100000;"
echo " println!(\"Benchmarking $SPEC...\");"
echo ""
echo " // Instantiation benchmark"
echo " let start = Instant::now();"
echo " for i in 0..iterations {"
echo " let mut m = $CONSTRUCT;"
echo " let _ = m;"
echo " }"
echo " let elapsed = start.elapsed();"
echo " let per = elapsed / iterations;"
echo " println!(\" Instantiate x{}:\", iterations);"
echo " println!(\" Total: {:?}\", elapsed);"
echo " println!(\" Per op: {:?}\", per);"
echo " println!(\" Ops/sec: {:.0}\", iterations as f64 / elapsed.as_secs_f64());"
echo ""
echo " // Operational benchmark"
echo " let mut total_ops = 0u64;"
echo " let start = Instant::now();"
echo " for i in 0..iterations {"
echo $PROLOGUE
echo " }"
echo " let elapsed = start.elapsed();"
echo " let per = elapsed / total_ops;"
echo " println!(\" Operations x{} ({} ops):\", iterations, total_ops);"
echo " let rate = total_ops as f64 / elapsed.as_secs_f64();"
echo " println!(\" Total: {:?}\", elapsed);"
echo " println!(\" Per op: {:?}\", per);"
echo " if rate > 1_000_000.0 {"
echo " println!(\" Rate: {:.0} M ops/sec\", rate / 1_000_000.0);"
echo " } else if rate > 1_000.0 {"
echo " println!(\" Rate: {:.0} K ops/sec\", rate / 1_000.0);"
echo " } else {"
echo " println!(\" Rate: {:.0} ops/sec\", rate);"
echo " }"
echo "}"
} >> "$BENCH_DIR/benches/bench.rs"
}
# KafkaKRaft benchmark
bench_spec "KafkaKRaft" \
' let mut m = KafkaKRaftMachine::new();
let _ = m.request_vote(2, 1, 0);
let _ = m.append_entries(2);
let _ = m.handle_request_vote_request(2, 1, 0, 0);
total_ops += 3;' \
'KafkaKRaftMachine::new()' \
3
# SQLitePager benchmark
bench_spec "SQLitePager" \
' let mut m = SQLitePagerMachine::new();
let _ = m.begin_read_transaction();
let _ = m.begin_write_transaction();
let _ = m.write_page(1, 100);
let _ = m.commit_transaction();
total_ops += 4;' \
'SQLitePagerMachine::new()' \
4
# TigerBeetleLedger benchmark
bench_spec "TigerBeetleLedger" \
' let mut m = TigerBeetleLedgerMachine::new();
let _ = m.create_account(1, 1000, 0);
let _ = m.create_account(2, 0, 0);
let _ = m.create_transfer(1, 1, 2, 100, 0);
total_ops += 3;' \
'TigerBeetleLedgerMachine::new()' \
3
echo ""
echo "=== Building benchmarks ==="
for D in out/KafkaKRaft-bench out/SQLitePager-bench out/TigerBeetleLedger-bench; do
echo "Building $D..."
(cd "$D" && cargo bench --no-run 2>&1 | tail -5)
done
echo ""
echo "=== Running benchmarks ==="
for D in out/KafkaKRaft-bench out/SQLitePager-bench out/TigerBeetleLedger-bench; do
echo ""
(cd "$D" && cargo bench 2>&1)
done