diff --git a/library/modules/platform/android.sx b/library/modules/platform/android.sx index 98885ba..12aec5e 100644 --- a/library/modules/platform/android.sx +++ b/library/modules/platform/android.sx @@ -135,35 +135,19 @@ sx_android_activity_clazz :: (activity: *void) -> *void { } // Declarative JNI class bindings for the safe-insets dispatch chain -// (Phase 2D migration). Each `#jni_class` declares the sx-side alias, -// the JNI class path, and the methods we use — `inst.method(args)` -// inside a `#jni_env(env) { ... }` scope lowers to the same JNI vtable -// indirection as the hand-rolled `#jni_call` form, with the descriptor -// auto-derived from the sx signature (see [src/ir/jni_descriptor.zig]). -WindowInsets :: #jni_class("android/view/WindowInsets") { - getSystemWindowInsetTop :: (self: *Self) -> s32; - getSystemWindowInsetLeft :: (self: *Self) -> s32; - getSystemWindowInsetBottom :: (self: *Self) -> s32; - getSystemWindowInsetRight :: (self: *Self) -> s32; -} - -View :: #jni_class("android/view/View") { - getRootWindowInsets :: (self: *Self) -> *WindowInsets; -} - -Window :: #jni_class("android/view/Window") { - getDecorView :: (self: *Self) -> *View; -} - -Activity :: #jni_class("android/app/Activity") { - getWindow :: (self: *Self) -> *Window; -} +// live in a named sub-module so they don't collide with consumer-side +// types (e.g. `modules/ui/view.sx`'s `View` protocol). The compiler +// registers the foreign-class decls inside under both qualified +// (`Jni.Activity`) and bare (`Activity`) names — receiver types use +// the qualified form, cross-class refs in method signatures use the +// bare form. +Jni :: #import "modules/platform/android_jni.sx"; // sx-side reimplementation of the JNI dispatch chain. Caller provides // an already-attached `JNIEnv*` and the activity's `clazz` jobject -// (cast to `*Activity` so the method-call DSL can find it in the -// foreign-class registry). Outputs physical-pixel insets. -sx_query_safe_insets_jni :: (env: *void, activity: *Activity, top: *s32, left: *s32, bottom: *s32, right: *s32) -> void { +// (cast to `*Jni.Activity` so the method-call DSL can route it through +// the foreign-class registry). Outputs physical-pixel insets. +sx_query_safe_insets_jni :: (env: *void, activity: *Jni.Activity, top: *s32, left: *s32, bottom: *s32, right: *s32) -> void { inline if OS != .android { return; } top.* = 0; left.* = 0; bottom.* = 0; right.* = 0; @@ -421,7 +405,7 @@ impl Platform for AndroidPlatform { attached : bool = false; env := sx_android_get_env(g_android_activity, @attached); if env != null { - clazz : *Activity = xx sx_android_activity_clazz(g_android_activity); + clazz : *Jni.Activity = xx sx_android_activity_clazz(g_android_activity); sx_query_safe_insets_jni(env, clazz, @t, @l, @b, @r); if attached { sx_android_detach_env(g_android_activity); } } diff --git a/library/modules/platform/android_jni.sx b/library/modules/platform/android_jni.sx new file mode 100644 index 0000000..fdb6b1a --- /dev/null +++ b/library/modules/platform/android_jni.sx @@ -0,0 +1,31 @@ +// Declarative JNI class bindings used by the Android platform module's +// safe-insets dispatch chain. Imported under a named namespace from +// `modules/platform/android.sx` so the bare class names (`Activity`, +// `Window`, `View`, `WindowInsets`) don't pollute the top-level +// namespace when consumers flat-import the platform module — `View` +// in particular collides with `modules/ui/view.sx`'s protocol. +// +// Inside the platform module these are referenced as `Jni.Activity`, +// `Jni.Window`, etc. The compiler registers the decls both qualified +// and bare in `foreign_class_map`, so cross-class refs in method +// signatures (`getWindow :: (self: *Self) -> *Window`) still resolve +// against the bare name within the namespace. + +WindowInsets :: #jni_class("android/view/WindowInsets") { + getSystemWindowInsetTop :: (self: *Self) -> s32; + getSystemWindowInsetLeft :: (self: *Self) -> s32; + getSystemWindowInsetBottom :: (self: *Self) -> s32; + getSystemWindowInsetRight :: (self: *Self) -> s32; +} + +View :: #jni_class("android/view/View") { + getRootWindowInsets :: (self: *Self) -> *WindowInsets; +} + +Window :: #jni_class("android/view/Window") { + getDecorView :: (self: *Self) -> *View; +} + +Activity :: #jni_class("android/app/Activity") { + getWindow :: (self: *Self) -> *Window; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 733f66c..dd38ffe 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -367,6 +367,7 @@ pub const Lowering = struct { self.registerForeignClassDecl(&decl.data.foreign_class_decl); }, .namespace_decl => |ns| { + self.registerNamespacedForeignClasses(ns); if (self.main_file != null) { self.lowerDecls(ns.decls); } @@ -507,6 +508,7 @@ pub const Lowering = struct { self.registerForeignClassDecl(&decl.data.foreign_class_decl); }, .namespace_decl => |ns| { + self.registerNamespacedForeignClasses(ns); if (self.main_file != null) { self.scanDecls(ns.decls); } @@ -8121,6 +8123,25 @@ pub const Lowering = struct { self.foreign_class_map.put(fcd.name, fcd) catch {}; } + /// When a namespaced import (`Ns :: #import "..."`) contains foreign-class + /// declarations, ALSO register them under their qualified name `Ns.Class` + /// so receiver types like `*Ns.Class` can find the fcd. The recursive + /// scan/lower already handles bare-name registration; this only adds the + /// qualified-name entry, so cross-class refs in method signatures + /// (`*View` → bare lookup) still work. + fn registerNamespacedForeignClasses(self: *Lowering, ns: ast.NamespaceDecl) void { + for (ns.decls) |inner| { + if (inner.data == .foreign_class_decl) { + const fcd = &inner.data.foreign_class_decl; + const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ ns.name, fcd.name }) catch fcd.name; + self.foreign_class_map.put(qualified, fcd) catch {}; + } else if (inner.data == .namespace_decl) { + // Nested namespaces — qualify with both prefixes. + self.registerNamespacedForeignClasses(inner.data.namespace_decl); + } + } + } + /// Register an impl block: register its methods as TypeName.method in fn_ast_map. fn registerImplBlock(self: *Lowering, ib: *const ast.ImplBlock, is_imported: bool, decl: *const Node) void { // Parameterised-protocol impl (e.g. `impl Into(Block) for Closure() -> void`):