ffi #jni_main: emit name: Type; body members as private Java fields

`#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;`.
This commit is contained in:
agra
2026-05-20 17:01:24 +03:00
parent d946e3d577
commit 36f40057f7
2 changed files with 53 additions and 0 deletions

View File

@@ -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 = &registry });
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);

View File

@@ -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) {