ffi 2.10: #jni_method_descriptor override precedence in deriveMethod
When a `ForeignMethodDecl` carries a non-null `jni_descriptor_override` (parsed in step 2.6), `deriveMethod` short-circuits — auto-derivation is skipped entirely and the override is returned (duplicated through the caller's allocator so ownership semantics stay uniform regardless of which branch ran). Two new tests: override beats normal derivation, and override bypasses cross-class refs that would otherwise fail with `UnknownClassAlias`. Confirms the escape-hatch semantics from 2.6 — users can paste an explicit JNI signature when auto-derivation doesn't match (synthetic methods, ambiguous overloads, unknown-to-sx JVM internals). 15 jni_descriptor tests pass; 126/126 examples still green.
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user