// JNI descriptor derivation for #jni_class methods (Phase 2 step 2.8). // // Walks sx parameter / return type AST nodes through the standard JNI // signature alphabet (JLS §4.3.3 and JNI spec §3.3) to produce the // descriptor string consumed by `GetMethodID` / `GetStaticMethodID`. // // void → V // bool → Z (jboolean) // s8 → B (jbyte) // s16 → S (jshort) // s32 → I (jint) // s64 → J (jlong) // u8 → B (jbyte — JNI bytes are signed; sx u8 still bridges via B) // u16 → C (jchar — unsigned 16-bit) // f32 → F (jfloat) // f64 → D (jdouble) // []T → [ // [*]T → [ (sx many-pointer treated as array for now) // *Self → L; // *Foo → L; (cross-class — step 2.9) // // `#jni_method_descriptor("...")` (step 2.6) overrides this whole walk // when set; sema/lowering use the override verbatim. const std = @import("std"); const ast = @import("../ast.zig"); const Node = ast.Node; pub const DeriveError = error{ UnknownPrimitive, UnknownClassAlias, // *Foo where Foo isn't a declared #jni_class UnsupportedType, 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`. /// A null type_node represents a void return ('V'). pub fn writeType( allocator: std.mem.Allocator, buf: *std.ArrayList(u8), ctx: Context, type_node: ?*const Node, ) DeriveError!void { if (type_node == null) { try buf.append(allocator, 'V'); return; } const tn = type_node.?; switch (tn.data) { .type_expr => |te| { const ch = primitiveChar(te.name) orelse return DeriveError.UnknownPrimitive; try buf.append(allocator, ch); }, .slice_type_expr => |sl| { try buf.append(allocator, '['); try writeType(allocator, buf, ctx, sl.element_type); }, .many_pointer_type_expr => |mp| { try buf.append(allocator, '['); try writeType(allocator, buf, ctx, mp.element_type); }, .array_type_expr => |arr| { try buf.append(allocator, '['); try writeType(allocator, buf, ctx, arr.element_type); }, .pointer_type_expr => |ptr| { // *Self → L;, *Foo → L;, // *void → Ljava/lang/Object; (opaque jobject — common when // users don't have a precise Java type for the value). const inner = ptr.pointee_type; 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 (std.mem.eql(u8, target_name, "void")) "java/lang/Object" 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, } } /// Derives the full `(args)ret` method descriptor for a `ForeignMethodDecl`. /// The first param is skipped when `is_static == false` (it's the implicit /// `self: *Self` receiver, which doesn't appear in the JNI descriptor). pub fn deriveMethod( allocator: std.mem.Allocator, 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); try buf.append(allocator, '('); const start: usize = if (method.is_static) 0 else 1; if (method.params.len < start) return DeriveError.UnsupportedType; for (method.params[start..]) |param| { try writeType(allocator, &buf, ctx, param); } try buf.append(allocator, ')'); try writeType(allocator, &buf, ctx, method.return_type); return buf.toOwnedSlice(allocator); } fn primitiveChar(name: []const u8) ?u8 { const table = [_]struct { name: []const u8, ch: u8 }{ .{ .name = "void", .ch = 'V' }, .{ .name = "bool", .ch = 'Z' }, .{ .name = "s8", .ch = 'B' }, .{ .name = "u8", .ch = 'B' }, .{ .name = "s16", .ch = 'S' }, .{ .name = "u16", .ch = 'C' }, .{ .name = "s32", .ch = 'I' }, .{ .name = "s64", .ch = 'J' }, .{ .name = "f32", .ch = 'F' }, .{ .name = "f64", .ch = 'D' }, }; for (table) |entry| { if (std.mem.eql(u8, name, entry.name)) return entry.ch; } return null; }