Skip to content

Commit 8abeaaa

Browse files
fix(res-to-affine): restore the five test fixtures deleted by f766dcb (#741)
## What Restores `tools/res-to-affine/test/fixtures/{partial1,phase2c,phase3,phase3b,phase3c}.res`, deleted by f766dcb *"chore: remove rescript example configuration"* (2026-08-18). `sample.res` was restored on main previously; these five were not. ## Why they are not "rescript example configuration" 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 `expected/*.affine` is compared. They are the only correctness check the ReScript→AffineScript migration tool has. ## Why it was silent Two independent failure-to-signal mechanisms: 1. `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 compiling and each test died at runtime on `Sys_error("fixtures/<f>.res: No such file or directory")`. 2. `test_walker.ml`'s `skip_unless_ready ()` calls `Alcotest.skip ()` when the tree-sitter grammar is not built. In an environment without the grammar all 32 walker cases SKIP and alcotest reports **`Test Successful … 0 test run`, exit 0** — so the suite is green precisely when it is testing nothing. ## Evidence With the grammar built and the fixtures absent: **32 of 32 walker tests fail**. With the fixtures restored: **32 of 32 pass**, and repo-wide `dune runtest` goes rc=1 → rc=0. ## Not fixed here Mechanism (2) above — the fail-open skip — is left as-is deliberately; turning a skip into a hard failure is a policy change for the maintainer, not part of a restore. Filed as a follow-up. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent b585f13 commit 8abeaaa

5 files changed

Lines changed: 212 additions & 0 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// #488 partial-port fixture: module-top-level functions -> `fn` skeletons
3+
// with switch->match, pipe desugaring, if/else, blocks, array/record literals,
4+
// and best-effort expression translation. Output is NOT expected to
5+
// type-check; it must parse, with un-translatable forms (e.g. an
6+
// interpolated template string) as TODO holes.
7+
8+
let classify = x => switch x {
9+
| Some(n) => n + 1
10+
| None => 0
11+
}
12+
13+
let area = (w, h) => w *. h
14+
15+
let greet = name => "hi " ++ name
16+
17+
let log2 = msg => Js.log(msg)
18+
19+
// pipe-first desugars: x->doStuff(1) -> doStuff(x, 1)
20+
let piped = x => x->doStuff(1)
21+
22+
// pipe chain desugars left-to-right: x->f->g(2) -> g(f(x), 2)
23+
let chain = x => x->f->g(2)
24+
25+
// if/else
26+
let clamp = x => if x > 0 { x } else { 0 }
27+
28+
// block with a let statement
29+
let scaled = x => { let y = x + 1; y * 2 }
30+
31+
// array literal -> [x, x]
32+
let pair = x => [x, x]
33+
34+
// record literal -> Rec #{ x: x, y: y } (nominal placeholder type)
35+
let mkpt = (x, y) => {x: x, y: y}
36+
37+
// interpolated template string has no handler yet -> must become a TODO hole.
38+
let tmpl = x => `val=${x}`
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Synthetic fixture for the two anti-patterns that were explicitly
3+
// deferred from Phase 1 entirely because they need real AST:
4+
// 1. inline-callback-record — 3+ inline function values in a single
5+
// record literal or a single call's labelled-argument list
6+
// 2. oversized-function — function spans more than 50 source rows
7+
// Walker-only by construction; the line-regex scanner does not detect
8+
// either pattern.
9+
10+
open Types
11+
12+
// --- inline-callback-record: a record literal with 4 inline lambdas
13+
let handlers = {
14+
onMount: () => Js.log("mounted"),
15+
onUnmount: () => Js.log("unmounted"),
16+
onClick: e => Js.log(e),
17+
onHover: e => Js.log(e),
18+
}
19+
20+
// --- inline-callback-record at a call site: 3 labelled-argument lambdas
21+
let _ = Widget.make(
22+
~onMount=() => Js.log("mounted"),
23+
~onUnmount=() => Js.log("unmounted"),
24+
~onClick=e => Js.log(e),
25+
)
26+
27+
// --- oversized-function: 60 source-row span. Body intentionally tedious
28+
// to surface the row-span proxy the walker uses.
29+
let huge = id => {
30+
let a01 = id + 1
31+
let a02 = a01 + 1
32+
let a03 = a02 + 1
33+
let a04 = a03 + 1
34+
let a05 = a04 + 1
35+
let a06 = a05 + 1
36+
let a07 = a06 + 1
37+
let a08 = a07 + 1
38+
let a09 = a08 + 1
39+
let a10 = a09 + 1
40+
let a11 = a10 + 1
41+
let a12 = a11 + 1
42+
let a13 = a12 + 1
43+
let a14 = a13 + 1
44+
let a15 = a14 + 1
45+
let a16 = a15 + 1
46+
let a17 = a16 + 1
47+
let a18 = a17 + 1
48+
let a19 = a18 + 1
49+
let a20 = a19 + 1
50+
let a21 = a20 + 1
51+
let a22 = a21 + 1
52+
let a23 = a22 + 1
53+
let a24 = a23 + 1
54+
let a25 = a24 + 1
55+
let a26 = a25 + 1
56+
let a27 = a26 + 1
57+
let a28 = a27 + 1
58+
let a29 = a28 + 1
59+
let a30 = a29 + 1
60+
let a31 = a30 + 1
61+
let a32 = a31 + 1
62+
let a33 = a32 + 1
63+
let a34 = a33 + 1
64+
let a35 = a34 + 1
65+
let a36 = a35 + 1
66+
let a37 = a36 + 1
67+
let a38 = a37 + 1
68+
let a39 = a38 + 1
69+
let a40 = a39 + 1
70+
let a41 = a40 + 1
71+
let a42 = a41 + 1
72+
let a43 = a42 + 1
73+
let a44 = a43 + 1
74+
let a45 = a44 + 1
75+
let a46 = a45 + 1
76+
let a47 = a46 + 1
77+
let a48 = a47 + 1
78+
let a49 = a48 + 1
79+
let a50 = a49 + 1
80+
let a51 = a50 + 1
81+
let a52 = a51 + 1
82+
a52
83+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Synthetic fixture for Phase 3 slice 1: structural type-declaration
3+
// translation. The first three type declarations are fully structural
4+
// and #228-independent, so the walker's --translate path renders them
5+
// as compilable AffineScript:
6+
// type userId = int -> type UserId = Int
7+
// type color = Red | ... -> type Color = | Red | Green | Blue
8+
// type shape = Circle(...) -> type Shape = | Circle(Float) | Rect(Int, Int)
9+
// The trailing `let`/`switch` is NOT a type declaration and stays a TODO
10+
// island (absent from the translation list). The generic `box` below now
11+
// translates too (slice 2: type parameters); the qualified-path decl stays
12+
// skipped (qualified-path RHS is deferred — it would parse but not resolve).
13+
14+
type userId = int
15+
16+
type color =
17+
| Red
18+
| Green
19+
| Blue
20+
21+
type shape =
22+
| Circle(float)
23+
| Rect(int, int)
24+
25+
// Slice 2: type parameters now translate -> type Box[A] = | Box(A)
26+
type box<'a> = Box('a)
27+
28+
// Skipped: qualified-path RHS (deferred).
29+
type theirMap = Belt.Map.t
30+
31+
// Not a type declaration: stays a TODO island.
32+
let area = s =>
33+
switch s {
34+
| Circle(r) => r *. r
35+
| Rect(w, h) => float_of_int(w * h)
36+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Phase 3 slice 2 fixture: record types (-> struct) and generics.
3+
// type point = {x: int, y: int} -> struct Point { x: Int, y: Int }
4+
// type box<'a> = {value: 'a} -> struct Box[A] { value: A }
5+
// type id<'a> = 'a -> type Id[A] = A
6+
// Records with `mutable` or optional `?` fields are SKIPPED (their
7+
// semantics can't be dropped).
8+
9+
type point = {
10+
x: int,
11+
y: int,
12+
}
13+
14+
type box<'a> = {
15+
value: 'a,
16+
}
17+
18+
type id<'a> = 'a
19+
20+
// SKIPPED: mutable field — AffineScript struct fields can't carry it.
21+
type counter = {
22+
mutable count: int,
23+
}
24+
25+
// SKIPPED: optional field — `?` has no struct equivalent.
26+
type config = {
27+
verbose?: bool,
28+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
// Phase 3 slice 3 fixture: module-level `let <id> = <literal>` -> `const`.
3+
// let answer = 42 -> const answer: Int = 42;
4+
// let pi = 3.14 -> const pi: Float = 3.14;
5+
// let greeting = "hi" -> const greeting: String = "hi";
6+
// let enabled = true -> const enabled: Bool = true;
7+
// Non-literal / ref / destructuring bindings are SKIPPED (not compile-time
8+
// constants, or not a plain identifier).
9+
10+
let answer = 42
11+
12+
let pi = 3.14
13+
14+
let greeting = "hi"
15+
16+
let enabled = true
17+
18+
let disabled = false
19+
20+
// SKIPPED: non-literal body (a call) — not a compile-time constant.
21+
let now = Date.now()
22+
23+
// SKIPPED: ref body is the mutable-global anti-pattern, not a const.
24+
let counter = ref(0)
25+
26+
// SKIPPED: destructuring pattern, not a plain identifier.
27+
let (a, b) = (1, 2)

0 commit comments

Comments
 (0)