ffi 2.9: cross-class *Foo resolves via ForeignClassDecl registry

`Context` gains an optional `classes: ?*const ClassRegistry` lookup
(sx alias → foreign path). `writeType`'s pointer-type arm now treats
`*Self` and `*Foo` uniformly: `Self` resolves to `enclosing_path`,
any other named target is looked up in the registry. Missing
registry or missing key both surface as `UnknownClassAlias`.

`DeriveError.CrossClassRefNotYetSupported` retired in favour of
`UnknownClassAlias` — the new error fires for both
"no-registry-provided" and "alias-not-in-registry", giving the
caller (later, sema with a real diagnostic) one error variant to
handle.

Four new unit tests: cross-class resolves with registry, errors
without registry, errors with empty registry, and end-to-end
`deriveMethod` with chained `*Self`/`*Foo` (`getDecorView ::
(self: *Self) -> *View → ()Landroid/view/View;`).

13 jni_descriptor tests pass; 126/126 examples still green.
This commit is contained in:
agra
2026-05-20 10:26:03 +03:00
parent 21c49066e5
commit 51882656a5
2 changed files with 103 additions and 12 deletions

View File

@@ -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 = &registry,
}, 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 = &registry,
}, 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 = &registry,
}, method);
defer a.free(out);
try std.testing.expectEqualStrings("()Landroid/view/View;", out);
}
test "deriveMethod skips implicit self for instance methods" {