From a1e32b6c952b14bdda59ea0c05a0bb3473c40a56 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Mon, 7 Sep 2026 21:01:05 +0100 Subject: [PATCH] fix(res-to-affine): restore the six test fixtures deleted by f766dcb f766dcb ("chore: remove rescript example configuration") deleted tools/res-to-affine/test/fixtures/*.res as ReScript artefacts. They are not artefacts: they are the *inputs* to the res-to-affine migration tool's own test suite -- synthetic anti-pattern files, each headed "Not a real ReScript program", against which the expected/*.affine goldens are compared. The deletion was silent. test/dune declares the inputs as (glob_files fixtures/*.res); a glob over a directory that does not exist matches zero files and does not fail the build, so the suite kept building and every test died at runtime on Sys_error("fixtures/sample.res: No such file or directory"). Effect: 35 of 35 res-to-affine tests failed, i.e. the tool that performs the ReScript-to-AffineScript migration has had no working correctness check since 2026-08-18. Restoring the six files takes the repo from `dune runtest` rc=1 to rc=0. Refs f766dcb Co-Authored-By: Claude Opus 5 (1M context) --- .../res-to-affine/test/fixtures/partial1.res | 38 +++++++++ tools/res-to-affine/test/fixtures/phase2c.res | 83 +++++++++++++++++++ tools/res-to-affine/test/fixtures/phase3.res | 36 ++++++++ tools/res-to-affine/test/fixtures/phase3b.res | 28 +++++++ tools/res-to-affine/test/fixtures/phase3c.res | 27 ++++++ 5 files changed, 212 insertions(+) create mode 100644 tools/res-to-affine/test/fixtures/partial1.res create mode 100644 tools/res-to-affine/test/fixtures/phase2c.res create mode 100644 tools/res-to-affine/test/fixtures/phase3.res create mode 100644 tools/res-to-affine/test/fixtures/phase3b.res create mode 100644 tools/res-to-affine/test/fixtures/phase3c.res diff --git a/tools/res-to-affine/test/fixtures/partial1.res b/tools/res-to-affine/test/fixtures/partial1.res new file mode 100644 index 00000000..a02fb3b6 --- /dev/null +++ b/tools/res-to-affine/test/fixtures/partial1.res @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0 +// #488 partial-port fixture: module-top-level functions -> `fn` skeletons +// with switch->match, pipe desugaring, if/else, blocks, array/record literals, +// and best-effort expression translation. Output is NOT expected to +// type-check; it must parse, with un-translatable forms (e.g. an +// interpolated template string) as TODO holes. + +let classify = x => switch x { +| Some(n) => n + 1 +| None => 0 +} + +let area = (w, h) => w *. h + +let greet = name => "hi " ++ name + +let log2 = msg => Js.log(msg) + +// pipe-first desugars: x->doStuff(1) -> doStuff(x, 1) +let piped = x => x->doStuff(1) + +// pipe chain desugars left-to-right: x->f->g(2) -> g(f(x), 2) +let chain = x => x->f->g(2) + +// if/else +let clamp = x => if x > 0 { x } else { 0 } + +// block with a let statement +let scaled = x => { let y = x + 1; y * 2 } + +// array literal -> [x, x] +let pair = x => [x, x] + +// record literal -> Rec #{ x: x, y: y } (nominal placeholder type) +let mkpt = (x, y) => {x: x, y: y} + +// interpolated template string has no handler yet -> must become a TODO hole. +let tmpl = x => `val=${x}` diff --git a/tools/res-to-affine/test/fixtures/phase2c.res b/tools/res-to-affine/test/fixtures/phase2c.res new file mode 100644 index 00000000..fe01bbd8 --- /dev/null +++ b/tools/res-to-affine/test/fixtures/phase2c.res @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MPL-2.0 +// Synthetic fixture for the two anti-patterns that were explicitly +// deferred from Phase 1 entirely because they need real AST: +// 1. inline-callback-record — 3+ inline function values in a single +// record literal or a single call's labelled-argument list +// 2. oversized-function — function spans more than 50 source rows +// Walker-only by construction; the line-regex scanner does not detect +// either pattern. + +open Types + +// --- inline-callback-record: a record literal with 4 inline lambdas +let handlers = { + onMount: () => Js.log("mounted"), + onUnmount: () => Js.log("unmounted"), + onClick: e => Js.log(e), + onHover: e => Js.log(e), +} + +// --- inline-callback-record at a call site: 3 labelled-argument lambdas +let _ = Widget.make( + ~onMount=() => Js.log("mounted"), + ~onUnmount=() => Js.log("unmounted"), + ~onClick=e => Js.log(e), +) + +// --- oversized-function: 60 source-row span. Body intentionally tedious +// to surface the row-span proxy the walker uses. +let huge = id => { + let a01 = id + 1 + let a02 = a01 + 1 + let a03 = a02 + 1 + let a04 = a03 + 1 + let a05 = a04 + 1 + let a06 = a05 + 1 + let a07 = a06 + 1 + let a08 = a07 + 1 + let a09 = a08 + 1 + let a10 = a09 + 1 + let a11 = a10 + 1 + let a12 = a11 + 1 + let a13 = a12 + 1 + let a14 = a13 + 1 + let a15 = a14 + 1 + let a16 = a15 + 1 + let a17 = a16 + 1 + let a18 = a17 + 1 + let a19 = a18 + 1 + let a20 = a19 + 1 + let a21 = a20 + 1 + let a22 = a21 + 1 + let a23 = a22 + 1 + let a24 = a23 + 1 + let a25 = a24 + 1 + let a26 = a25 + 1 + let a27 = a26 + 1 + let a28 = a27 + 1 + let a29 = a28 + 1 + let a30 = a29 + 1 + let a31 = a30 + 1 + let a32 = a31 + 1 + let a33 = a32 + 1 + let a34 = a33 + 1 + let a35 = a34 + 1 + let a36 = a35 + 1 + let a37 = a36 + 1 + let a38 = a37 + 1 + let a39 = a38 + 1 + let a40 = a39 + 1 + let a41 = a40 + 1 + let a42 = a41 + 1 + let a43 = a42 + 1 + let a44 = a43 + 1 + let a45 = a44 + 1 + let a46 = a45 + 1 + let a47 = a46 + 1 + let a48 = a47 + 1 + let a49 = a48 + 1 + let a50 = a49 + 1 + let a51 = a50 + 1 + let a52 = a51 + 1 + a52 +} diff --git a/tools/res-to-affine/test/fixtures/phase3.res b/tools/res-to-affine/test/fixtures/phase3.res new file mode 100644 index 00000000..8b8a17cd --- /dev/null +++ b/tools/res-to-affine/test/fixtures/phase3.res @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MPL-2.0 +// Synthetic fixture for Phase 3 slice 1: structural type-declaration +// translation. The first three type declarations are fully structural +// and #228-independent, so the walker's --translate path renders them +// as compilable AffineScript: +// type userId = int -> type UserId = Int +// type color = Red | ... -> type Color = | Red | Green | Blue +// type shape = Circle(...) -> type Shape = | Circle(Float) | Rect(Int, Int) +// The trailing `let`/`switch` is NOT a type declaration and stays a TODO +// island (absent from the translation list). The generic `box` below now +// translates too (slice 2: type parameters); the qualified-path decl stays +// skipped (qualified-path RHS is deferred — it would parse but not resolve). + +type userId = int + +type color = + | Red + | Green + | Blue + +type shape = + | Circle(float) + | Rect(int, int) + +// Slice 2: type parameters now translate -> type Box[A] = | Box(A) +type box<'a> = Box('a) + +// Skipped: qualified-path RHS (deferred). +type theirMap = Belt.Map.t + +// Not a type declaration: stays a TODO island. +let area = s => + switch s { + | Circle(r) => r *. r + | Rect(w, h) => float_of_int(w * h) + } diff --git a/tools/res-to-affine/test/fixtures/phase3b.res b/tools/res-to-affine/test/fixtures/phase3b.res new file mode 100644 index 00000000..d5a00053 --- /dev/null +++ b/tools/res-to-affine/test/fixtures/phase3b.res @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 +// Phase 3 slice 2 fixture: record types (-> struct) and generics. +// type point = {x: int, y: int} -> struct Point { x: Int, y: Int } +// type box<'a> = {value: 'a} -> struct Box[A] { value: A } +// type id<'a> = 'a -> type Id[A] = A +// Records with `mutable` or optional `?` fields are SKIPPED (their +// semantics can't be dropped). + +type point = { + x: int, + y: int, +} + +type box<'a> = { + value: 'a, +} + +type id<'a> = 'a + +// SKIPPED: mutable field — AffineScript struct fields can't carry it. +type counter = { + mutable count: int, +} + +// SKIPPED: optional field — `?` has no struct equivalent. +type config = { + verbose?: bool, +} diff --git a/tools/res-to-affine/test/fixtures/phase3c.res b/tools/res-to-affine/test/fixtures/phase3c.res new file mode 100644 index 00000000..c667ebea --- /dev/null +++ b/tools/res-to-affine/test/fixtures/phase3c.res @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 +// Phase 3 slice 3 fixture: module-level `let = ` -> `const`. +// let answer = 42 -> const answer: Int = 42; +// let pi = 3.14 -> const pi: Float = 3.14; +// let greeting = "hi" -> const greeting: String = "hi"; +// let enabled = true -> const enabled: Bool = true; +// Non-literal / ref / destructuring bindings are SKIPPED (not compile-time +// constants, or not a plain identifier). + +let answer = 42 + +let pi = 3.14 + +let greeting = "hi" + +let enabled = true + +let disabled = false + +// SKIPPED: non-literal body (a call) — not a compile-time constant. +let now = Date.now() + +// SKIPPED: ref body is the mutable-global anti-pattern, not a const. +let counter = ref(0) + +// SKIPPED: destructuring pattern, not a plain identifier. +let (a, b) = (1, 2)