diff --git a/src/ir/lower.test.zig b/src/ir/lower.test.zig index f957eb8..263bb4f 100644 --- a/src/ir/lower.test.zig +++ b/src/ir/lower.test.zig @@ -765,6 +765,55 @@ test "lower: objcPropertyKind defaults + explicit ARC modifiers" { try std.testing.expect(lowering.objc().objcPropertyKind(.{ .name = "raw", .field_type = obj_ty, .is_property = true, .property_modifiers = &assign_mods }) == .assign); } +// ── A6.2 scaffolding: pure JNI decision helpers ───────────────────── +// Lock JNI native-name mangling and return-type support before they move +// out of lower.zig (to the JNI module) in A6.2 sub-step 2. + +const lower_mod = @import("lower.zig"); + +test "lower: jniMangleNativeName mangles package path + method (/ -> _, _ -> _1)" { + const alloc = std.testing.allocator; + + // Plain path + method: `/` separators collapse to `_`, `Java_` prefix, + // `_sx_1` infix before the (mangled) method name. + const m1 = try lower_mod.jniMangleNativeName(alloc, "com/sx/App", "tick"); + defer alloc.free(m1); + try std.testing.expectEqualStrings("Java_com_sx_App_sx_1tick", m1); + + // Underscores in BOTH the path and the method escape to `_1` (so the JNI + // resolver can round-trip them), distinct from the `/`->`_` separator. + const m2 = try lower_mod.jniMangleNativeName(alloc, "a_b/C", "do_it"); + defer alloc.free(m2); + try std.testing.expectEqualStrings("Java_a_1b_C_sx_1do_1it", m2); +} + +test "lower: isJniReturnTypeSupported accepts the dispatchable set + pointers only" { + const alloc = std.testing.allocator; + var module = ir_mod.Module.init(alloc); + defer module.deinit(); + const t = &module.types; + + // The CallMethod-dispatchable primitives. + inline for (.{ TypeId.void, TypeId.bool, TypeId.s32, TypeId.s64, TypeId.f32, TypeId.f64 }) |ty| { + try std.testing.expect(lower_mod.isJniReturnTypeSupported(t, ty)); + } + + // Other primitive widths are NOT dispatchable (would hit emit_llvm's + // undef-producing `else` arm — the footgun this predicate guards). + inline for (.{ TypeId.s8, TypeId.s16, TypeId.u8, TypeId.u32, TypeId.u64 }) |ty| { + try std.testing.expect(!lower_mod.isJniReturnTypeSupported(t, ty)); + } + + // Pointer / many-pointer returns route through CallObjectMethod → true. + try std.testing.expect(lower_mod.isJniReturnTypeSupported(t, module.types.ptrTo(.void))); + try std.testing.expect(lower_mod.isJniReturnTypeSupported(t, module.types.manyPtrTo(.u8))); + + // A pass-by-value struct return is unsupported. + const sname = module.types.internString("CGRectish"); + const sty = module.types.intern(.{ .@"struct" = .{ .name = sname, .fields = &.{} } }); + try std.testing.expect(!lower_mod.isJniReturnTypeSupported(t, sty)); +} + // ── Pack projection name resolution (Feature 1, Step 2.2) ──────────── const errors = @import("../errors.zig"); diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 2573880..e8da498 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -16590,7 +16590,7 @@ pub fn isJniReturnTypeSupported(table: *const @import("types.zig").TypeTable, re /// `Java___sx_1`. JNI mangling: /// `/` → `_`, `_` → `_1`. The `sx_` prefix matches the Java-side /// `private native sx_(...)` delegate. -fn jniMangleNativeName(allocator: std.mem.Allocator, foreign_path: []const u8, method_name: []const u8) ![]u8 { +pub fn jniMangleNativeName(allocator: std.mem.Allocator, foreign_path: []const u8, method_name: []const u8) ![]u8 { var buf = std.ArrayList(u8).empty; try buf.appendSlice(allocator, "Java_"); for (foreign_path) |ch| {