From 36f40057f71e4e3917439880592c31321dc85da4 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 20 May 2026 17:01:24 +0300 Subject: [PATCH] ffi #jni_main: emit `name: Type;` body members as private Java fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `#jni_class` body items of the form `name: Type;` were parsed into `ForeignFieldDecl` but dropped by jni_java_emit. They now render as private Java fields between the static init block and the @Override delegates, using the same primitive / `*Foo` → fully-qualified-name type mapping as method parameters. Needed for the chess-on-Pixel `SxApp` Activity to hold its `SurfaceView` reference: `view: SurfaceView;` → `private android.view.SurfaceView view;`. --- src/ir/jni_java_emit.test.zig | 39 +++++++++++++++++++++++++++++++++++ src/ir/jni_java_emit.zig | 14 +++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/ir/jni_java_emit.test.zig b/src/ir/jni_java_emit.test.zig index c0e8cbc..f016dfd 100644 --- a/src/ir/jni_java_emit.test.zig +++ b/src/ir/jni_java_emit.test.zig @@ -269,6 +269,45 @@ test "lib_name renders System.loadLibrary static init block" { try std.testing.expect(std.mem.indexOf(u8, out2, "System.loadLibrary") == null); } +test "field declarations render as private Java fields" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + const surface_view_ty = try makePointer(aa, try makeTypeExpr(aa, "SurfaceView")); + const int_ty = try makeTypeExpr(aa, "s32"); + const self_ty = try makePointer(aa, try makeTypeExpr(aa, "Self")); + const body = try makeBodyMarker(aa); + + const view_field: ast.ForeignClassMember = .{ .field = .{ .name = "view", .field_type = surface_view_ty } }; + const w_field: ast.ForeignClassMember = .{ .field = .{ .name = "viewport_w", .field_type = int_ty } }; + const method: ast.ForeignClassMember = .{ .method = .{ + .name = "onCreate", + .params = &.{self_ty}, + .param_names = &.{"self"}, + .return_type = null, + .body = body, + } }; + + var registry = std.StringHashMap([]const u8).init(a); + defer registry.deinit(); + try registry.put("SurfaceView", "android/view/SurfaceView"); + + const fcd: ast.ForeignClassDecl = .{ + .name = "SxApp", + .foreign_path = "co/example/SxApp", + .runtime = .jni_class, + .is_main = true, + .members = &.{ view_field, w_field, method }, + }; + const out = try emit.emitJavaSource(a, &fcd, .{ .classes = ®istry }); + defer a.free(out); + + try std.testing.expect(std.mem.indexOf(u8, out, "private android.view.SurfaceView view;") != null); + try std.testing.expect(std.mem.indexOf(u8, out, "private int viewport_w;") != null); +} + test "#implements clauses on the class header" { const a = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(a); diff --git a/src/ir/jni_java_emit.zig b/src/ir/jni_java_emit.zig index 6a5f35e..df50306 100644 --- a/src/ir/jni_java_emit.zig +++ b/src/ir/jni_java_emit.zig @@ -125,6 +125,20 @@ pub fn emitJavaSource( try buf.appendSlice(allocator, "\"); }\n"); } + // Fields. `name: Type;` body items render as private Java fields — + // primitive types pass through, pointer types resolve to fully + // qualified Java class names via the class registry. + for (fcd.members) |m| switch (m) { + .field => |fd| { + try buf.appendSlice(allocator, " private "); + try emitJavaType(allocator, &buf, fd.field_type, opts); + try buf.append(allocator, ' '); + try buf.appendSlice(allocator, fd.name); + try buf.appendSlice(allocator, ";\n"); + }, + else => {}, + }; + // Two passes: @Override stubs + native delegate; then the native // declarations. for (fcd.members) |m| switch (m) {