From 22768d9adf3c7822ebfdc0fa6df8fa2385ec879f Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 20 May 2026 16:41:13 +0300 Subject: [PATCH] ffi #jni_main: drop implicit super call from @Override delegate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Java @Override no longer injects a `super.(...)` call before the native delegate. The user calls super from the sx-side body when needed — via a forthcoming `super.method(args)` dispatch lowered to `CallNonvirtualMethod` on JNI classes. Two reasons: - Interface method impls (e.g. SurfaceHolder.Callback) have no super to call. The previous emit produced javac-rejected code for those. - Lifecycle overrides may want to skip super in some cases, or call it with different args. The emitter can't second-guess intent. User-space control of the dispatch keeps the emitter free of "is this an interface method or a supertype override?" guesswork. The dex shrinks by one virtual-method bytecode invocation per override. Caveat: until the sx-side `super.method(args)` dispatch lands, Activity lifecycle methods (onCreate, onResume, etc.) that mandate `super.<>` will throw `SuperNotCalledException` at runtime if their bodies don't do their own JNI dispatch. The slice 2 smoke still launches cleanly because its onCreate body is empty. 131 host / 4 cross / zig build test all green. --- src/ir/jni_java_emit.test.zig | 3 +-- src/ir/jni_java_emit.zig | 13 ++++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/ir/jni_java_emit.test.zig b/src/ir/jni_java_emit.test.zig index 912f683..36476d8 100644 --- a/src/ir/jni_java_emit.test.zig +++ b/src/ir/jni_java_emit.test.zig @@ -88,7 +88,6 @@ test "void onCreate(Bundle) with default Activity superclass" { \\public class SxNativeActivity extends android.app.Activity { \\ @Override \\ public void onCreate(android.os.Bundle b) { - \\ super.onCreate(b); \\ sx_onCreate(b); \\ } \\ private native void sx_onCreate(android.os.Bundle b); @@ -126,7 +125,7 @@ test "primitive params" { defer a.free(out); try std.testing.expect(std.mem.indexOf(u8, out, "public void onWindowFocusChanged(boolean hasFocus)") != null); - try std.testing.expect(std.mem.indexOf(u8, out, "super.onWindowFocusChanged(hasFocus);") != null); + try std.testing.expect(std.mem.indexOf(u8, out, "super.onWindowFocusChanged") == null); // emitter never injects super try std.testing.expect(std.mem.indexOf(u8, out, "sx_onWindowFocusChanged(hasFocus);") != null); try std.testing.expect(std.mem.indexOf(u8, out, "private native void sx_onWindowFocusChanged(boolean hasFocus);") != null); } diff --git a/src/ir/jni_java_emit.zig b/src/ir/jni_java_emit.zig index c133645..76ae2d3 100644 --- a/src/ir/jni_java_emit.zig +++ b/src/ir/jni_java_emit.zig @@ -171,17 +171,20 @@ fn emitOverride( md: ast.ForeignMethodDecl, opts: Options, ) EmitError!void { + // The Java @Override only calls the native delegate. `super.(...)` + // is NOT injected — if the user wants to invoke the supertype's + // implementation (e.g. `super.onCreate(b)` for an Activity lifecycle hook + // that requires it) they call super from the sx-side body via JNI + // dispatch. This keeps the emitter free of "is this an interface method + // or a supertype override?" guesswork and matches the sx principle of + // user-space code expressing the dispatch. try buf.appendSlice(allocator, " @Override\n public "); try emitJavaReturnType(allocator, buf, md.return_type, opts); try buf.append(allocator, ' '); try buf.appendSlice(allocator, md.name); try buf.append(allocator, '('); try emitJavaParamList(allocator, buf, md, opts); - try buf.appendSlice(allocator, ") {\n super."); - try buf.appendSlice(allocator, md.name); - try buf.append(allocator, '('); - try emitJavaArgList(allocator, buf, md); - try buf.appendSlice(allocator, ");\n sx_"); + try buf.appendSlice(allocator, ") {\n sx_"); try buf.appendSlice(allocator, md.name); try buf.append(allocator, '('); try emitJavaArgList(allocator, buf, md);