diff --git a/src/ir/jni_descriptor.test.zig b/src/ir/jni_descriptor.test.zig index cadf9a8..21d0a83 100644 --- a/src/ir/jni_descriptor.test.zig +++ b/src/ir/jni_descriptor.test.zig @@ -160,6 +160,58 @@ test "cross-class *Foo with empty registry errors" { try std.testing.expectError(desc.DeriveError.UnknownClassAlias, result); } +test "deriveMethod respects #jni_method_descriptor override verbatim" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + // The actual sx signature `(self: *Self) -> s32` would derive to + // `()I`. The override should win regardless. + const self_te = try makeTypeExpr(aa, "Self"); + const self_ptr = try makePointer(aa, self_te); + const ret = try makeTypeExpr(aa, "s32"); + + const method: ast.ForeignMethodDecl = .{ + .name = "weirdMethod", + .params = &.{self_ptr}, + .param_names = &.{"self"}, + .return_type = ret, + .is_static = false, + .jni_descriptor_override = "(Ljava/lang/Object;)Ljava/util/List;", + }; + const out = try desc.deriveMethod(a, .{ .enclosing_path = "com/example/Foo" }, method); + defer a.free(out); + try std.testing.expectEqualStrings("(Ljava/lang/Object;)Ljava/util/List;", out); +} + +test "deriveMethod override bypasses unresolvable cross-class refs" { + const a = std.testing.allocator; + var arena = std.heap.ArenaAllocator.init(a); + defer arena.deinit(); + const aa = arena.allocator(); + + // The signature references `*UnknownClass` that isn't in the + // registry — would normally fail with `UnknownClassAlias`. The + // override short-circuits derivation, so it succeeds. + const self_te = try makeTypeExpr(aa, "Self"); + const self_ptr = try makePointer(aa, self_te); + const unknown = try makeTypeExpr(aa, "UnknownClass"); + const unknown_ptr = try makePointer(aa, unknown); + + const method: ast.ForeignMethodDecl = .{ + .name = "weirdMethod", + .params = &.{self_ptr}, + .param_names = &.{"self"}, + .return_type = unknown_ptr, + .is_static = false, + .jni_descriptor_override = "()V", + }; + const out = try desc.deriveMethod(a, .{ .enclosing_path = "com/example/Foo" }, method); + defer a.free(out); + try std.testing.expectEqualStrings("()V", out); +} + test "deriveMethod chains *Foo returns and params" { const a = std.testing.allocator; var arena = std.heap.ArenaAllocator.init(a); diff --git a/src/ir/jni_descriptor.zig b/src/ir/jni_descriptor.zig index 0eafd5c..c0b2085 100644 --- a/src/ir/jni_descriptor.zig +++ b/src/ir/jni_descriptor.zig @@ -105,6 +105,13 @@ pub fn deriveMethod( ctx: Context, method: ast.ForeignMethodDecl, ) DeriveError![]u8 { + // `#jni_method_descriptor("(Sig)Ret")` short-circuits derivation + // entirely. Allocate a copy so the caller has uniform ownership + // semantics regardless of which branch ran. + if (method.jni_descriptor_override) |override| { + return allocator.dupe(u8, override); + } + var buf: std.ArrayList(u8) = .empty; errdefer buf.deinit(allocator);