test(ir): lock pure JNI decision helpers before A6.2 extraction (A6.2 scaffolding step 1)

Test-first scaffolding for the JNI FFI domain (Phase A6.2) before the pure
helpers move out of lower.zig. Visibility-only change — no behavior change.

- 2 new lower.test.zig tests for the pure JNI helpers lacking unit coverage:
  - jniMangleNativeName: `/`->`_` separator, `_`->`_1` escape (path AND method),
    `Java_` prefix, `_sx_1` infix (2 cases lock all rules).
  - isJniReturnTypeSupported: void/bool/s32/s64/f32/f64 + pointer/many-pointer
    -> true; other widths (s8/s16/u8/u32/u64) + by-value struct -> false.
- JNI descriptor derivation (writeType/deriveMethod) is already extracted into
  jni_descriptor.zig (15 tests) — not part of A6.2.
- Widened jniMangleNativeName -> pub (file-scope free fn; isJniReturnTypeSupported
  already pub). Reached from the test via ir_mod.lower.*. No logic touched.
- Recorded the A6.2 coverage inventory + residual emission-bound gaps
  (synthesizeJniMainStub*/lowerJniCall/lowerJniConstructor/lowerSuperCall/
  getJniEnvTlFids stay in lower.zig; jniMapParamType is a trivial resolveType
  wrapper) in ARCH-SAFETY.md.

Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0
(no .ir churn; 9 JNI .ir snapshots green).
This commit is contained in:
agra
2026-06-03 08:14:46 +03:00
parent 9bde1dd590
commit 0a4a240e31
2 changed files with 50 additions and 1 deletions

View File

@@ -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 Call<T>Method-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");

View File

@@ -16590,7 +16590,7 @@ pub fn isJniReturnTypeSupported(table: *const @import("types.zig").TypeTable, re
/// `Java_<pkg-mangled>_<Class>_sx_1<method-mangled>`. JNI mangling:
/// `/` → `_`, `_` → `_1`. The `sx_` prefix matches the Java-side
/// `private native sx_<name>(...)` 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| {