Skip to content

Commit 7008fea

Browse files
mjambonclaude
andauthored
atdj: generate wrapper classes for top-level list aliases (#500)
* atdj: generate wrapper classes for top-level list aliases Java has no type aliases, so 'type items = item list' previously caused an error. We now generate a class Items implementing Atdj with: - a no-arg constructor (empty list) - a constructor from ArrayList<Item> - a constructor from a JSON string - a package-private constructor from JSONArray (analogous to the JSONObject constructor generated for records) - toJsonBuffer / toJson - a public 'value' field of type ArrayList<Item> The assign function is also updated so that list aliases used as record fields or sum-variant payloads are deserialized via 'new Items(jsonArray)' rather than triggering the inline ArrayList-building loop. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update CHANGES.md for atdj list alias support Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * atdj: cast keys().next() to String for newer org.json JSONObject.keys() returns Iterator<Object> in newer versions of org.json, causing a compile error. The returned key is always a String in practice, so a cast is safe. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7a8f5ef commit 7008fea

8 files changed

Lines changed: 173 additions & 14 deletions

File tree

CHANGES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
4.1.1 (unreleased)
2+
------------------
3+
4+
* atdj: Top-level list aliases such as `type items = item list` are now
5+
supported. A wrapper class (e.g. `Items`) implementing `Atdj` is generated
6+
with the same interface as record classes: a no-arg constructor, a
7+
constructor from `ArrayList<T>`, a constructor from a JSON string, a
8+
package-private constructor from `JSONArray`, `toJsonBuffer`, `toJson`,
9+
and a public `value` field of type `ArrayList<T>`. List aliases used as
10+
record fields or sum-variant payloads are also handled correctly.
11+
112
4.1.0 (2026-04-11)
213
------------------
314

atdj/src/atdj_helper.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Util {
4747
JSONObject obj = (JSONObject)value;
4848
if (obj.length() != 1)
4949
throw new JSONException(\"Expected single-key object for sum type\");
50-
return obj.keys().next();
50+
return (String) obj.keys().next();
5151
}
5252
else throw new JSONException(\"Cannot extract type\");
5353
}

atdj/src/atdj_trans.ml

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ let rec assign env opt_dst src java_ty atd_ty indent =
6767
sprintf "new %s(%s)" java_ty src
6868
| Record _ ->
6969
sprintf "new %s(%s)" java_ty src
70+
| List _ when not (String.contains java_ty '<') ->
71+
(* Named list alias class - use the JSONArray constructor *)
72+
sprintf "new %s(%s)" java_ty src
7073
| Name (_, (_, ty, _), _) ->
7174
(match Atd.Type_name.basename ty with
7275
| "bool" | "int" | "float" | "string" -> src
@@ -81,18 +84,22 @@ let rec assign env opt_dst src java_ty atd_ty indent =
8184
| Record _ ->
8285
sprintf "%s%s = new %s(%s);\n" indent dst java_ty src
8386
| List (_, sub_ty, _) ->
84-
let java_sub_ty = (*ahem*) extract_from_edgy_brackets java_ty in
85-
let sub_expr = assign env None "_tmp" java_sub_ty sub_ty "" in
87+
if String.contains java_ty '<' then
88+
let java_sub_ty = (*ahem*) extract_from_edgy_brackets java_ty in
89+
let sub_expr = assign env None "_tmp" java_sub_ty sub_ty "" in
8690

87-
sprintf "%s%s = new %s();\n" indent dst java_ty
88-
^ sprintf "%sfor (int _i = 0; _i < %s.length(); ++_i) {\n" indent src
91+
sprintf "%s%s = new %s();\n" indent dst java_ty
92+
^ sprintf "%sfor (int _i = 0; _i < %s.length(); ++_i) {\n" indent src
8993

90-
^ sprintf "%s %s _tmp = %s.%s(_i);\n" indent
91-
(json_of_atd env sub_ty) src (get env sub_ty false)
94+
^ sprintf "%s %s _tmp = %s.%s(_i);\n" indent
95+
(json_of_atd env sub_ty) src (get env sub_ty false)
9296

93-
^ sprintf "%s %s.add(%s);\n" indent
94-
dst sub_expr
95-
^ sprintf "%s}\n" indent
97+
^ sprintf "%s %s.add(%s);\n" indent
98+
dst sub_expr
99+
^ sprintf "%s}\n" indent
100+
else
101+
(* Named list alias class - use the JSONArray constructor *)
102+
sprintf "%s%s = new %s(%s);\n" indent dst java_ty src
96103

97104
| Name (_, (_, ty, _), _) ->
98105
(match Atd.Type_name.basename ty with
@@ -315,11 +322,90 @@ and trans_outer env (def : A.type_def) =
315322
trans_sum name env (loc, v, a)
316323
| Record (loc, v, a) ->
317324
trans_record name env (loc, v, a)
318-
| Name (_, (_, _name, _), _) ->
319-
(* Don't translate primitive types at the top-level *)
320-
env
325+
| List (_, sub_ty, _) ->
326+
trans_list_alias name env sub_ty
327+
| Name (_, (_, _name, _), _) as ty ->
328+
(match norm_ty env ty with
329+
| List (_, sub_ty, _) -> trans_list_alias name env sub_ty
330+
| _ -> (* Don't translate primitive types at the top-level *) env)
321331
| x -> type_not_supported x
322332

333+
(* Translation of a top-level list alias, e.g. 'type items = item list'.
334+
* Since Java has no type aliases, we generate a class Items that wraps an
335+
* ArrayList and provides the same JSON interface as record classes.
336+
*)
337+
and trans_list_alias my_name env sub_atd_ty =
338+
let class_name = Atdj_names.to_class_name my_name in
339+
let (java_sub_ty, env) = trans_inner env sub_atd_ty in
340+
let json_sub_ty = json_of_atd env sub_atd_ty in
341+
let get_method = get env sub_atd_ty false in
342+
let sub_expr = assign env None "_tmp" java_sub_ty sub_atd_ty "" in
343+
let ctor_body =
344+
sprintf " for (int _i = 0; _i < ja.length(); ++_i) {\n"
345+
^ sprintf " %s _tmp = ja.%s(_i);\n" json_sub_ty get_method
346+
^ sprintf " value.add(%s);\n" sub_expr
347+
^ sprintf " }\n"
348+
in
349+
let to_json_body =
350+
sprintf " _out.append(\"[\");\n"
351+
^ sprintf " for (int i = 0; i < value.size(); ++i) {\n"
352+
^ to_string env "value.get(i)" sub_atd_ty " "
353+
^ sprintf " if (i < value.size() - 1)\n"
354+
^ sprintf " _out.append(\",\");\n"
355+
^ sprintf " }\n"
356+
^ sprintf " _out.append(\"]\");\n"
357+
in
358+
let out = open_class env class_name in
359+
fprintf out "\
360+
public class %s implements Atdj {
361+
/**
362+
* Construct a fresh empty list.
363+
*/
364+
public %s() {
365+
value = new java.util.ArrayList<%s>();
366+
}
367+
368+
/**
369+
* Construct from an existing list.
370+
*/
371+
public %s(java.util.ArrayList<%s> arr) {
372+
value = arr;
373+
}
374+
375+
/**
376+
* Construct from a JSON string.
377+
*/
378+
public %s(String s) throws JSONException {
379+
this(new JSONArray(s));
380+
}
381+
382+
%s(JSONArray ja) throws JSONException {
383+
value = new java.util.ArrayList<%s>();
384+
%s }
385+
386+
public void toJsonBuffer(StringBuilder _out) throws JSONException {
387+
%s }
388+
389+
public String toJson() throws JSONException {
390+
StringBuilder out = new StringBuilder(128);
391+
toJsonBuffer(out);
392+
return out.toString();
393+
}
394+
395+
public java.util.ArrayList<%s> value;
396+
}
397+
"
398+
class_name
399+
class_name java_sub_ty
400+
class_name java_sub_ty
401+
class_name
402+
class_name java_sub_ty
403+
ctor_body
404+
to_json_body
405+
java_sub_ty;
406+
close_out out;
407+
env
408+
323409
(* Translation of sum types. For a sum type
324410
*
325411
* type ty = Foo | Bar of whatever

atdj/test/com/mylife/test/dune

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
B.java
77
ComplexRecord.java
88
E.java
9+
Items.java
910
RecordWithDefaults.java
1011
SampleSum.java
1112
Shape.java

atdj/test/dune

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
(package atdj)
2525
(action (diff expected/E.java com/mylife/test/E.java)))
2626

27+
(rule
28+
(alias runtest)
29+
(package atdj)
30+
(action (diff expected/Items.java com/mylife/test/Items.java)))
31+
2732
(rule
2833
(alias runtest)
2934
(package atdj)

atdj/test/expected/Items.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Automatically generated; do not edit
2+
package com.mylife.test;
3+
import org.json.*;
4+
5+
public class Items implements Atdj {
6+
/**
7+
* Construct a fresh empty list.
8+
*/
9+
public Items() {
10+
value = new java.util.ArrayList<SimpleRecord>();
11+
}
12+
13+
/**
14+
* Construct from an existing list.
15+
*/
16+
public Items(java.util.ArrayList<SimpleRecord> arr) {
17+
value = arr;
18+
}
19+
20+
/**
21+
* Construct from a JSON string.
22+
*/
23+
public Items(String s) throws JSONException {
24+
this(new JSONArray(s));
25+
}
26+
27+
Items(JSONArray ja) throws JSONException {
28+
value = new java.util.ArrayList<SimpleRecord>();
29+
for (int _i = 0; _i < ja.length(); ++_i) {
30+
JSONObject _tmp = ja.getJSONObject(_i);
31+
value.add(new SimpleRecord(_tmp));
32+
}
33+
}
34+
35+
public void toJsonBuffer(StringBuilder _out) throws JSONException {
36+
_out.append("[");
37+
for (int i = 0; i < value.size(); ++i) {
38+
value.get(i).toJsonBuffer(_out);
39+
if (i < value.size() - 1)
40+
_out.append(",");
41+
}
42+
_out.append("]");
43+
}
44+
45+
public String toJson() throws JSONException {
46+
StringBuilder out = new StringBuilder(128);
47+
toJsonBuffer(out);
48+
return out.toString();
49+
}
50+
51+
public java.util.ArrayList<SimpleRecord> value;
52+
}

atdj/test/expected/Util.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ else if (value instanceof JSONObject) {
2020
JSONObject obj = (JSONObject)value;
2121
if (obj.length() != 1)
2222
throw new JSONException("Expected single-key object for sum type");
23-
return obj.keys().next();
23+
return (String) obj.keys().next();
2424
}
2525
else throw new JSONException("Cannot extract type");
2626
}

atdj/test/test.atd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ type simple_record = { ?o : bool option }
4848
type a = [ A of b list ]
4949
type b = [ B ]
5050

51+
(* Type alias for a list - Java doesn't support type aliases, so we generate
52+
a wrapper class with the same JSON interface as record classes. *)
53+
type items = simple_record list
54+
5155
(*
5256
Sum type using the object encoding: {"Constructor": payload}
5357
instead of the default array encoding: ["Constructor", payload].

0 commit comments

Comments
 (0)