diff --git a/src/ir/jni_descriptor.test.zig b/src/ir/jni_descriptor.test.zig index f3662f6..cadf9a8 100644 --- a/src/ir/jni_descriptor.test.zig +++ b/src/ir/jni_descriptor.test.zig @@ -99,7 +99,30 @@ test "slice of primitive is array descriptor" { try std.testing.expectEqualStrings("[I", buf.items); } -test "cross-class *Foo is not yet supported (2.9)" { +test "cross-class *Foo resolves via class registry" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + var registry = desc.ClassRegistry.init(a); + defer registry.deinit(); + try registry.put("Window", "android/view/Window"); + try registry.put("View", "android/view/View"); + + const foo = try makeTypeExpr(aa, "Window"); + const ptr = try makePointer(aa, foo); + + var buf: std.ArrayList(u8) = .empty; + defer buf.deinit(a); + try desc.writeType(a, &buf, .{ + .enclosing_path = "android/view/View", + .classes = ®istry, + }, ptr); + try std.testing.expectEqualStrings("Landroid/view/Window;", buf.items); +} + +test "cross-class *Foo without registry errors" { const a = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(a); defer arena.deinit(); @@ -110,8 +133,64 @@ test "cross-class *Foo is not yet supported (2.9)" { var buf: std.ArrayList(u8) = .empty; defer buf.deinit(a); - const result = desc.writeType(a, &buf, .{ .enclosing_path = "android/view/View" }, ptr); - try std.testing.expectError(desc.DeriveError.CrossClassRefNotYetSupported, result); + const result = desc.writeType(a, &buf, .{ + .enclosing_path = "android/view/View", + }, ptr); + try std.testing.expectError(desc.DeriveError.UnknownClassAlias, result); +} + +test "cross-class *Foo with empty registry errors" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + var registry = desc.ClassRegistry.init(a); + defer registry.deinit(); + + const foo = try makeTypeExpr(aa, "WindowInsets"); + const ptr = try makePointer(aa, foo); + + var buf: std.ArrayList(u8) = .empty; + defer buf.deinit(a); + const result = desc.writeType(a, &buf, .{ + .enclosing_path = "android/view/Window", + .classes = ®istry, + }, ptr); + try std.testing.expectError(desc.DeriveError.UnknownClassAlias, result); +} + +test "deriveMethod chains *Foo returns and params" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + var registry = desc.ClassRegistry.init(a); + defer registry.deinit(); + try registry.put("Window", "android/view/Window"); + try registry.put("View", "android/view/View"); + try registry.put("WindowInsets", "android/view/WindowInsets"); + + // getDecorView :: (self: *Self) -> *View → ()Landroid/view/View; + const self_te = try makeTypeExpr(aa, "Self"); + const self_ptr = try makePointer(aa, self_te); + const view_te = try makeTypeExpr(aa, "View"); + const view_ptr = try makePointer(aa, view_te); + + const method: ast.ForeignMethodDecl = .{ + .name = "getDecorView", + .params = &.{self_ptr}, + .param_names = &.{"self"}, + .return_type = view_ptr, + .is_static = false, + }; + const out = try desc.deriveMethod(a, .{ + .enclosing_path = "android/view/Window", + .classes = ®istry, + }, method); + defer a.free(out); + try std.testing.expectEqualStrings("()Landroid/view/View;", out); } test "deriveMethod skips implicit self for instance methods" { diff --git a/src/ir/jni_descriptor.zig b/src/ir/jni_descriptor.zig index e2f70d3..0eafd5c 100644 --- a/src/ir/jni_descriptor.zig +++ b/src/ir/jni_descriptor.zig @@ -29,15 +29,23 @@ const Node = ast.Node; pub const DeriveError = error{ UnknownPrimitive, + UnknownClassAlias, // *Foo where Foo isn't a declared #jni_class UnsupportedType, - CrossClassRefNotYetSupported, // *Foo for non-Self — lands in step 2.9 OutOfMemory, }; +/// Map from sx-side alias → foreign path of declared `#jni_class` / +/// `#jni_interface` decls. Used to resolve `*Foo` into `L;` in +/// the descriptor. Built during lowering's scan pass. +pub const ClassRegistry = std.StringHashMap([]const u8); + pub const Context = struct { /// Foreign path of the enclosing #jni_class — used to resolve `*Self`. /// e.g. "android/view/View". enclosing_path: []const u8, + /// Lookup for sibling/forward-declared `#jni_class` aliases. When null, + /// only `*Self` resolves; any other pointer-to-named-type errors. + classes: ?*const ClassRegistry = null, }; /// Appends a single JNI type-descriptor to `buf` for `type_node`. @@ -71,15 +79,19 @@ pub fn writeType( try writeType(allocator, buf, ctx, arr.element_type); }, .pointer_type_expr => |ptr| { - // *Self → L; + // *Self → L;, *Foo → L; const inner = ptr.pointee_type; - if (inner.data == .type_expr and std.mem.eql(u8, inner.data.type_expr.name, "Self")) { - try buf.append(allocator, 'L'); - try buf.appendSlice(allocator, ctx.enclosing_path); - try buf.append(allocator, ';'); - return; - } - return DeriveError.CrossClassRefNotYetSupported; + if (inner.data != .type_expr) return DeriveError.UnsupportedType; + const target_name = inner.data.type_expr.name; + const target_path: []const u8 = if (std.mem.eql(u8, target_name, "Self")) + ctx.enclosing_path + else if (ctx.classes) |reg| + reg.get(target_name) orelse return DeriveError.UnknownClassAlias + else + return DeriveError.UnknownClassAlias; + try buf.append(allocator, 'L'); + try buf.appendSlice(allocator, target_path); + try buf.append(allocator, ';'); }, else => return DeriveError.UnsupportedType, }