diff --git a/src/ir/jni_java_emit.test.zig b/src/ir/jni_java_emit.test.zig index b5fad9f..912f683 100644 --- a/src/ir/jni_java_emit.test.zig +++ b/src/ir/jni_java_emit.test.zig @@ -235,3 +235,44 @@ test "default-package class (no slash in foreign_path)" { try std.testing.expect(std.mem.indexOf(u8, out, "package ") == null); try std.testing.expect(std.mem.indexOf(u8, out, "public class SxNoPackage") != null); } + +test "#implements clauses on the class header" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + const self_ty = try makePointer(aa, try makeTypeExpr(aa, "Self")); + const body = try makeBodyMarker(aa); + + // Two interfaces: one resolvable via the registry, one passed through verbatim. + const impl_a: ast.ForeignClassMember = .{ .implements = "Callback" }; + const impl_b: ast.ForeignClassMember = .{ .implements = "java.lang.Runnable" }; + 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("Callback", "android/view/SurfaceHolder$Callback"); + + const fcd: ast.ForeignClassDecl = .{ + .name = "SxApp", + .foreign_path = "co/example/SxApp", + .runtime = .jni_class, + .is_main = true, + .members = &.{ impl_a, impl_b, method }, + }; + const out = try emit.emitJavaSource(a, &fcd, .{ .classes = ®istry }); + defer a.free(out); + + try std.testing.expect(std.mem.indexOf( + u8, + out, + "public class SxApp extends android.app.Activity implements android.view.SurfaceHolder$Callback, java.lang.Runnable {", + ) != null); +} diff --git a/src/ir/jni_java_emit.zig b/src/ir/jni_java_emit.zig index 6e703ef..c133645 100644 --- a/src/ir/jni_java_emit.zig +++ b/src/ir/jni_java_emit.zig @@ -90,6 +90,27 @@ pub fn emitJavaSource( try buf.appendSlice(allocator, parts.cls); try buf.appendSlice(allocator, " extends "); try buf.appendSlice(allocator, parent); + + // `#implements Alias;` body items become Java `implements` clauses on the + // class header. Aliases resolve through the class registry the same way + // `#extends` does — an unmapped alias passes through verbatim (useful for + // referring to built-in JVM interfaces without declaring them). + var first_iface = true; + for (fcd.members) |m| switch (m) { + .implements => |alias| { + try buf.appendSlice(allocator, if (first_iface) " implements " else ", "); + first_iface = false; + if (opts.classes) |reg| { + if (reg.get(alias)) |path| { + try appendDotted(allocator, &buf, path); + continue; + } + } + try buf.appendSlice(allocator, alias); + }, + else => {}, + }; + try buf.appendSlice(allocator, " {\n"); // Two passes: @Override stubs that call super + native delegate,