Skip to content

Commit 4d4d1d4

Browse files
feat(res-to-affine): #488 partial-port slice 3 — array + record literals (Refs #488) (#496)
#488 partial-port slice 3: --partial now translates array literals ([a, b] -> [a, b]) and record literals ({x: x, y: y} -> Rec #{ x: x, y: y }, nominal placeholder type + field-punning expansion). JS objects / template strings / try-catch still become () /* TODO */ holes. Parses (no parse error); 32 walker tests. Refs #488.
1 parent 78906f9 commit 4d4d1d4

5 files changed

Lines changed: 70 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0
2020

2121
### Added
2222

23+
- feat(res-to-affine): partial-port mode #488 slice 3 — `--partial` now translates array literals (`[a, b]`) and record literals (`{x, y}``Rec #{ x: x, y: y }`, with a nominal placeholder type + field-punning expansion) (Refs #488)
2324
- feat(res-to-affine): partial-port mode #488 slice 2 — `--partial` now desugars ReScript pipe-first `->` (`a->f(b)``f(a, b)`, chained left-to-right), and translates `if`/`else` and blocks with `let` statements (Refs #488)
2425
- feat(res-to-affine): partial-port mode (#488) — new `--partial` flag renders module-top-level functions as AffineScript `fn` skeletons with `switch``match` and best-effort expression translation (literals / idents / calls / binary ops with float-op + identity-equality normalisation / `++` / member + qualified access / ternary / variant + tuple + literal patterns); un-translatable forms become `() /* TODO */` / `_ /* TODO */` holes. Output deliberately does NOT type-check but parses (verified). Distinct model from `--translate` (Refs #488)
2526
- feat(res-to-affine): Phase 3 slice 3 — `--translate` now also lowers module-level `let <id> = <literal>` (int/float/string/bool) to a typed `const name: T = value;`; call / `ref(...)` / destructuring bindings are skipped (not compile-time constants); every emitted form verified compilable via `main.exe check`. `switch``match` and qualified-path resolution remain out of the standalone-type-check scope (Refs #57)

tools/res-to-affine/README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,17 @@ It translates literals, identifiers, calls, binary operators (normalising
231231
ReScript's float ops `+.`/`*.``+`/`*` and `===`/`!==``==`/`!=`), string
232232
concat `++`, member/qualified access, ternaries, **`if`/`else`**, **blocks
233233
with `let` statements**, **pipe-first `->`** (`a->f(b)``f(a, b)`, chained
234-
left-to-right), and `switch``match` with variant/tuple/literal patterns.
235-
Anything else (records, arrays, objects, …) becomes a `() /* TODO */` hole.
236-
The output is a partial port to finish by hand: it **parses** but is not
237-
expected to type-check (verified — the generated skeletons reach
238-
resolution/type-checking without a parse error). Continuing under #488:
239-
record/array/object literals, labelled args, combining `--partial` with
240-
`--translate`, and module-qualified-reference *resolution*.
234+
left-to-right), **array literals** (`[a, b]`), **record literals** (`{x, y}`
235+
`Rec #{ x: x, y: y }` — AffineScript records are *nominal*, so an anonymous
236+
ReScript record gets the placeholder type `Rec` for the human to rename;
237+
field punning `{x}` expands to `x: x`), and `switch``match` with
238+
variant/tuple/literal patterns. Anything else (JS objects, interpolated
239+
template strings, `try`/`catch`, …) becomes a `() /* TODO */` hole. The output
240+
is a partial port to finish by hand: it **parses** but is not expected to
241+
type-check (verified — the generated skeletons reach resolution/type-checking
242+
without a parse error). Continuing under #488: JS objects / template strings,
243+
labelled-arg refinement, combining `--partial` with `--translate`, and
244+
module-qualified-reference *resolution* (a module-mapping policy decision).
241245

242246
## Corpus run
243247

tools/res-to-affine/test/fixtures/partial1.res

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
// SPDX-License-Identifier: MIT
22
// #488 partial-port fixture: module-top-level functions -> `fn` skeletons
3-
// with switch->match, pipe desugaring, if/else, blocks, and best-effort
4-
// expression translation. Output is NOT expected to type-check; it must
5-
// parse, with un-translatable forms (e.g. an array literal) as TODO holes.
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.
67

78
let classify = x => switch x {
89
| Some(n) => n + 1
@@ -27,5 +28,11 @@ let clamp = x => if x > 0 { x } else { 0 }
2728
// block with a let statement
2829
let scaled = x => { let y = x + 1; y * 2 }
2930

30-
// array literal has no handler yet -> must become a TODO hole.
31+
// array literal -> [x, x]
3132
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}`

tools/res-to-affine/test/test_walker.ml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,19 @@ let partial1_blob () =
401401
let test_partial_count () =
402402
skip_unless_ready ();
403403
Alcotest.(check int)
404-
"nine module-top-level functions -> fn skeletons"
405-
9 (List.length (translate_partial1 ()))
404+
"eleven module-top-level functions -> fn skeletons"
405+
11 (List.length (translate_partial1 ()))
406+
407+
let test_partial_array () =
408+
skip_unless_ready ();
409+
Alcotest.(check bool) "array literal translated"
410+
true (contains (partial1_blob ()) "[x, x]")
411+
412+
let test_partial_record () =
413+
skip_unless_ready ();
414+
(* nominal placeholder type `Rec`; field punning {x} -> x: x *)
415+
Alcotest.(check bool) "record literal -> Rec #{ ... }"
416+
true (contains (partial1_blob ()) "Rec #{ x: x, y: y }")
406417

407418
let test_partial_pipe () =
408419
skip_unless_ready ();
@@ -521,8 +532,12 @@ let () =
521532
] );
522533
( "walker-488-partial",
523534
[
524-
Alcotest.test_case "nine functions -> fn skeletons"
535+
Alcotest.test_case "eleven functions -> fn skeletons"
525536
`Quick test_partial_count;
537+
Alcotest.test_case "array literal translated"
538+
`Quick test_partial_array;
539+
Alcotest.test_case "record literal -> Rec #{ ... }"
540+
`Quick test_partial_record;
526541
Alcotest.test_case "switch -> match + patterns + arm bodies"
527542
`Quick test_partial_switch_to_match;
528543
Alcotest.test_case "float op normalised + multi-param skeleton"

tools/res-to-affine/walker.ml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,35 @@ let rec translate_expr ~source n =
10441044
Printf.sprintf "%s.%s" (translate_expr ~source r)
10451045
(node_text ~source p)
10461046
| _ -> todo_expr ~source n)
1047+
| "array" ->
1048+
Printf.sprintf "[%s]"
1049+
(String.concat ", "
1050+
(List.filter_map
1051+
(fun c ->
1052+
if c.ntype = "comment" then None
1053+
else Some (translate_expr ~source c))
1054+
n.children))
1055+
(* AffineScript records are nominal (`Type #{ … }`), so an anonymous ReScript
1056+
record gets a placeholder type `Rec` for the human to rename; the field
1057+
values are translated. Field punning `{x}` expands to `x: x`. *)
1058+
| "record" ->
1059+
let fields =
1060+
List.filter_map
1061+
(fun c ->
1062+
if c.ntype <> "record_field" then None
1063+
else
1064+
match List.filter (fun x -> x.ntype <> "comment") c.children with
1065+
| [ name ] ->
1066+
let nm = node_text ~source name in
1067+
Some (Printf.sprintf "%s: %s" nm nm)
1068+
| name :: value :: _ ->
1069+
Some
1070+
(Printf.sprintf "%s: %s" (node_text ~source name)
1071+
(translate_expr ~source value))
1072+
| [] -> None)
1073+
n.children
1074+
in
1075+
Printf.sprintf "Rec #{ %s }" (String.concat ", " fields)
10471076
| _ -> todo_expr ~source n
10481077

10491078
and translate_labeled_arg ~source n =

0 commit comments

Comments
 (0)