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:
@@ -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" {
|
||||
|
||||
@@ -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<path>;` 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<enclosing-foreign-path>;
|
||||
// *Self → L<enclosing>;, *Foo → L<Foo's foreign path>;
|
||||
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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user