ffi #jni_main: drop implicit super call from @Override delegate

The Java @Override no longer injects a `super.<method>(...)` 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
`CallNonvirtual<Type>Method` 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.
This commit is contained in:
agra
2026-05-20 16:41:13 +03:00
parent bce5448fe9
commit 22768d9adf
2 changed files with 9 additions and 7 deletions

View File

@@ -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);
}

View File

@@ -171,17 +171,20 @@ fn emitOverride(
md: ast.ForeignMethodDecl,
opts: Options,
) EmitError!void {
// The Java @Override only calls the native delegate. `super.<method>(...)`
// 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);