Skip to content

Commit d424436

Browse files
committed
feat(interop): D-526 drain 7 — java.util.Arrays common statics
Smell-audited: 1: asList returns the D-294 ArrayList host surface rather than mirroring the JVM's fixed-size write-through VIEW (no cljw consumer depends on that aliasing — noted in the surface doc); binarySearch/ hashCode/typed-primitive overloads stay omitted until a real consumer (dead-interop lesson). New surface runtime/java/util/Arrays.zig (keyword `arrays`, R1-R3 compliant): asList / toString ("[e1, e2, ...]", nil -> "null") / copyOf (nil-padded) / sort (in-place stable natural compare) / equals (element value equality, nil-safe) / fill. Corpus objects_static +9 clj-byte-matched goldens; zone_check strict green.
1 parent bc679be commit d424436

5 files changed

Lines changed: 221 additions & 0 deletions

File tree

‎.dev/debt.yaml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ active:
206206
Character/Math) is now clj-parity COMPLETE for the common surface; the
207207
remaining interop gaps are other packages (java.util.Arrays/Collections,
208208
String static char[] forms) — separate units, lower value.
209+
DRAIN 7 (2026-07-07): NEW surface java.util.Arrays (runtime/java/util/
210+
Arrays.zig + _host_api + compat_tiers keyword `arrays`). asList (returns
211+
the D-294 ArrayList host surface — the JVM fixed-size-VIEW aliasing is
212+
deliberately not mirrored) / toString ("[e1, e2, …]"; nil arg -> "null") /
213+
copyOf (nil-padded like Java's null pad) / sort (in-place natural
214+
`compare`, stable) / equals (element value equality, nil-safe) / fill.
215+
binarySearch / hashCode / typed-primitive overloads omitted until a real
216+
consumer (dead-interop lesson). Corpus: objects_static.txt +9 goldens
217+
(clj-byte-matched). REMAINING: java.util.Collections statics + String
218+
static char[] forms — next drains of this row.
209219
category: "interop"
210220
quality_floor: "public-ization: java-interop static-member gaps (ADR-0166)"
211221
barrier: |-

‎data/compat_tiers.yaml‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,18 @@ tiers:
273273
surface: runtime/java/util/Objects.zig
274274
methods: [isNull, nonNull, equals, requireNonNull, requireNonNullElse, toString]
275275
clojure_peer_vars: []
276+
# java.util.Arrays — common statics over the .array tag (D-526). asList
277+
# returns the ArrayList host surface; sort is in-place natural compare;
278+
# copyOf nil-pads; typed-primitive overloads omitted until a consumer.
279+
- fqn: java.util.Arrays
280+
cljw_ns: cljw.java.util.Arrays
281+
keyword: arrays
282+
tier: A
283+
phase: 14
284+
files:
285+
surface: runtime/java/util/Arrays.zig
286+
methods: [asList, toString, copyOf, sort, equals, fill]
287+
clojure_peer_vars: []
276288
# java.util.HashMap — mutable key→value map (D-425, host_instance backing a
277289
# cljw map Value; one map Value GC-traced via host_trace, reuses cljw
278290
# hashing/equality). seq yields cljw MapEntry pairs (AD-032).

‎src/runtime/java/_host_api.zig‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const java_surfaces = [_]type{
119119
@import("util/Random.zig"),
120120
@import("util/UUID.zig"),
121121
@import("util/Objects.zig"),
122+
@import("util/Arrays.zig"),
122123
// Matcher's backing impl is shipped by Pattern's regex/match.zig.
123124
@import("util/regex/Matcher.zig"),
124125
@import("util/regex/Pattern.zig"),

‎src/runtime/java/util/Arrays.zig‎

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// SPDX-License-Identifier: EPL-2.0
2+
//! Java surface for `java.util.Arrays` static methods (D-526).
3+
//!
4+
//! Backend: impl-only
5+
//! Impl deps: java_array
6+
//! Clojure peer: none
7+
//!
8+
//! The common static surface over cljw's `.array` tag (ADR-0105 JavaArray —
9+
//! Object[] semantics; Value slots): `asList` (an ArrayList host instance,
10+
//! the D-294 surface) / `toString` ("[e1, e2, …]") / `copyOf` (nil-padded
11+
//! like Java's null pad) / `sort` (in-place, natural `compare` order) /
12+
//! `equals` (element value equality) / `fill`. `binarySearch` / `hashCode` /
13+
//! the typed-primitive-array overload family are intentionally omitted until
14+
//! a real consumer needs them (dead-interop lesson).
15+
16+
const std = @import("std");
17+
const host_api = @import("../_host_api.zig");
18+
const type_descriptor = @import("../../type_descriptor.zig");
19+
const Value = @import("../../value/value.zig").Value;
20+
const Runtime = @import("../../runtime.zig").Runtime;
21+
const Env = @import("../../env.zig").Env;
22+
const SourceLocation = @import("../../error/info.zig").SourceLocation;
23+
const error_catalog = @import("../../error/catalog.zig");
24+
const java_array = @import("../../collection/java_array.zig");
25+
const array_list_surface = @import("ArrayList.zig");
26+
const vector_mod = @import("../../collection/vector.zig");
27+
const equal = @import("../../equal.zig");
28+
const compare_mod = @import("../../compare.zig");
29+
const print_mod = @import("../../print.zig");
30+
const string_mod = @import("../../collection/string.zig");
31+
32+
/// Arity-1 array-argument prelude shared by the statics.
33+
fn expectArray(v: Value, fn_name: []const u8, loc: SourceLocation) !void {
34+
if (!java_array.isArray(v))
35+
return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = fn_name, .expected = "a Java array", .actual = @tagName(v.tag()) });
36+
}
37+
38+
/// `(java.util.Arrays/asList arr)` — a java.util.ArrayList holding the
39+
/// array's elements. JVM returns a fixed-size List VIEW; cljw returns the
40+
/// existing ArrayList host surface (mutable, not write-through — the view
41+
/// aliasing is JVM-internal and no cljw consumer depends on it).
42+
fn asList(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value {
43+
try error_catalog.checkArity("java.util.Arrays/asList", args, 1, loc);
44+
try expectArray(args[0], "java.util.Arrays/asList", loc);
45+
const vec = try vector_mod.fromSlice(rt, java_array.asArray(args[0]).items());
46+
return array_list_surface.initArrayList(rt, env, &.{vec}, loc);
47+
}
48+
49+
/// `(java.util.Arrays/toString arr)` — "[e1, e2, …]" with `str`-form
50+
/// elements (Java String.valueOf). nil array → "null" (JVM parity).
51+
fn toStringFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value {
52+
try error_catalog.checkArity("java.util.Arrays/toString", args, 1, loc);
53+
if (args[0].tag() == .nil) return string_mod.alloc(rt, "null");
54+
try expectArray(args[0], "java.util.Arrays/toString", loc);
55+
var aw: std.Io.Writer.Allocating = .init(rt.gpa);
56+
defer aw.deinit();
57+
try aw.writer.writeByte('[');
58+
for (java_array.asArray(args[0]).items(), 0..) |e, i| {
59+
if (i > 0) try aw.writer.writeAll(", ");
60+
if (e.tag() == .nil) {
61+
try aw.writer.writeAll("null"); // Java String.valueOf(null)
62+
} else {
63+
try print_mod.writeStrValue(rt, env, &aw.writer, e);
64+
}
65+
}
66+
try aw.writer.writeByte(']');
67+
return string_mod.alloc(rt, aw.writer.buffered());
68+
}
69+
70+
/// `(java.util.Arrays/copyOf arr n)` — a fresh array of length `n`: truncated
71+
/// or nil-padded (Java pads Object[] with null). JVM ref: Arrays#copyOf.
72+
fn copyOf(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value {
73+
_ = env;
74+
try error_catalog.checkArity("java.util.Arrays/copyOf", args, 2, loc);
75+
try expectArray(args[0], "java.util.Arrays/copyOf", loc);
76+
const n_i64 = try error_catalog.expectInteger(args[1], "java.util.Arrays/copyOf", loc);
77+
if (n_i64 < 0)
78+
return error_catalog.raise(.type_arg_invalid, loc, .{ .fn_name = "java.util.Arrays/copyOf", .expected = "a non-negative length", .actual = "negative int" });
79+
const n: u32 = @intCast(n_i64);
80+
const out = try java_array.make(rt, n, .nil_val);
81+
const src = java_array.asArray(args[0]).items();
82+
const dst = java_array.asArray(out).items();
83+
const keep = @min(src.len, dst.len);
84+
@memcpy(dst[0..keep], src[0..keep]);
85+
return out;
86+
}
87+
88+
const SortCtx = struct {
89+
rt: *Runtime,
90+
loc: SourceLocation,
91+
err: ?anyerror = null,
92+
};
93+
94+
fn arrayLessThan(ctx: *SortCtx, a: Value, b: Value) bool {
95+
if (ctx.err != null) return false;
96+
const ord = compare_mod.valueCompare(ctx.rt, a, b, ctx.loc) catch |e| {
97+
ctx.err = e;
98+
return false;
99+
};
100+
return ord == .lt;
101+
}
102+
103+
/// `(java.util.Arrays/sort arr)` — IN-PLACE natural-order sort (cljw
104+
/// `compare`), returning nil like the void Java method. Stable block sort.
105+
fn sortFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value {
106+
_ = env;
107+
try error_catalog.checkArity("java.util.Arrays/sort", args, 1, loc);
108+
try expectArray(args[0], "java.util.Arrays/sort", loc);
109+
var ctx: SortCtx = .{ .rt = rt, .loc = loc };
110+
std.mem.sort(Value, java_array.asArray(args[0]).items(), &ctx, arrayLessThan);
111+
if (ctx.err) |e| return e;
112+
return .nil_val;
113+
}
114+
115+
/// `(java.util.Arrays/equals a b)` — same length + element value equality
116+
/// (nil-safe: two nils true, one nil false). JVM ref: Arrays#equals.
117+
fn equalsFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value {
118+
try error_catalog.checkArity("java.util.Arrays/equals", args, 2, loc);
119+
const a = args[0];
120+
const b = args[1];
121+
if (a.tag() == .nil and b.tag() == .nil) return .true_val;
122+
if (a.tag() == .nil or b.tag() == .nil) return .false_val;
123+
try expectArray(a, "java.util.Arrays/equals", loc);
124+
try expectArray(b, "java.util.Arrays/equals", loc);
125+
const as = java_array.asArray(a).items();
126+
const bs = java_array.asArray(b).items();
127+
if (as.len != bs.len) return .false_val;
128+
for (as, bs) |x, y| {
129+
if (!try equal.valueEqual(rt, env, x, y)) return .false_val;
130+
}
131+
return .true_val;
132+
}
133+
134+
/// `(java.util.Arrays/fill arr v)` — set every slot to `v`; returns nil
135+
/// (void in Java). JVM ref: Arrays#fill.
136+
fn fillFn(rt: *Runtime, env: *Env, args: []const Value, loc: SourceLocation) anyerror!Value {
137+
_ = rt;
138+
_ = env;
139+
try error_catalog.checkArity("java.util.Arrays/fill", args, 2, loc);
140+
try expectArray(args[0], "java.util.Arrays/fill", loc);
141+
@memset(java_array.asArray(args[0]).items(), args[1]);
142+
return .nil_val;
143+
}
144+
145+
fn initArrays(td: *type_descriptor.TypeDescriptor, gpa: std.mem.Allocator) anyerror!void {
146+
if (td.method_table.len != 0) return; // idempotent re-run
147+
const specs = .{
148+
.{ "asList", &asList },
149+
.{ "toString", &toStringFn },
150+
.{ "copyOf", &copyOf },
151+
.{ "sort", &sortFn },
152+
.{ "equals", &equalsFn },
153+
.{ "fill", &fillFn },
154+
};
155+
const entries = try gpa.alloc(type_descriptor.TypeDescriptor.MethodEntry, specs.len);
156+
inline for (specs, 0..) |spec, i| {
157+
entries[i] = .{
158+
.protocol_name = "",
159+
.method_name = try gpa.dupe(u8, spec[0]),
160+
.method_val = Value.initBuiltinFn(spec[1]),
161+
};
162+
}
163+
td.method_table = entries;
164+
}
165+
166+
pub const ___HOST_EXTENSION: host_api.Extension = .{
167+
.cljw_ns = "cljw.java.util.Arrays",
168+
.descriptor = &descriptor,
169+
.init = &initArrays,
170+
};
171+
172+
var descriptor: type_descriptor.TypeDescriptor = .{
173+
.fqcn = "cljw.java.util.Arrays",
174+
.kind = .native,
175+
.field_layout = null,
176+
.protocol_impls = &.{},
177+
.method_table = &.{},
178+
.parent = null,
179+
.meta = .nil_val,
180+
};

‎test/diff/clj_corpus/objects_static.txt‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,21 @@
4242
;;=> :npe
4343
(try (java.util.Objects/requireNonNull 5) :ok (catch NullPointerException _ :npe))
4444
;;=> :ok
45+
(vec (java.util.Arrays/asList (to-array [1 2 3])))
46+
;;=> [1 2 3]
47+
(java.util.Arrays/toString (to-array [1 :k "s"]))
48+
;;=> "[1, :k, s]"
49+
(java.util.Arrays/toString (to-array []))
50+
;;=> "[]"
51+
(let [a (to-array [3 1 2])] (java.util.Arrays/sort a) (vec a))
52+
;;=> [1 2 3]
53+
(vec (java.util.Arrays/copyOf (to-array [1 2 3]) 5))
54+
;;=> [1 2 3 nil nil]
55+
(vec (java.util.Arrays/copyOf (to-array [1 2 3]) 2))
56+
;;=> [1 2]
57+
(java.util.Arrays/equals (to-array [1 2]) (to-array [1 2]))
58+
;;=> true
59+
(java.util.Arrays/equals (to-array [1 2]) (to-array [1 3]))
60+
;;=> false
61+
(let [a (to-array [1 2 3])] (java.util.Arrays/fill a :x) (vec a))
62+
;;=> [:x :x :x]

0 commit comments

Comments
 (0)