test: group examples into per-category folders

Move examples/*.sx and their expected/ snapshots into per-category
subfolders (examples/<category>/...). Folder = leading filename token,
with ffi-objc/ffi-jni kept whole; filenames are unchanged. The corpus
runner and LSP sweep now discover each category's expected/ dir, while
issues/ stays flat. Example 1058's repo-root-relative companion import
is made file-relative. Path strings embedded in 164 snapshots were
regenerated (path-only changes). Test-layout docs in CLAUDE.md updated.
This commit is contained in:
agra
2026-06-21 14:41:34 +03:00
parent 6d1409bc1f
commit 66bdc70bf1
3357 changed files with 456 additions and 363 deletions

View File

@@ -0,0 +1,27 @@
// Phase 1 step 1.2 (PLAN-FFI.md): parser coverage for `#jni_call`
// and `#jni_static_call`. Same `#<intrinsic>(ReturnT)(args...)`
// shape as `#objc_call`; lands green on the existing parse rule
// (Phase 1.1 added all three).
//
// `inline if false` suppresses sema/codegen — the lowering arrives
// in Phase 1.15+; this file pins the parse surface only.
#import "modules/std.sx";
main :: () -> i32 {
inline if false {
env : *void = null;
#jni_env(env) {
// Instance method: target, name, sig, args...
#jni_call(*void)(null, "getWindow", "()Landroid/view/Window;");
// Static method: class, name, sig, args...
#jni_static_call(i32)(null, "max", "(II)I", 3, 7);
// Returning a Java primitive (jboolean → sx bool).
#jni_call(bool)(null, "isShown", "()Z");
}
}
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,45 @@
// Phase 1 step 1.15 (PLAN-FFI.md): `#jni_call(void)` codegen.
//
// `#jni_call(T)(env, target, "method_name", "(Sig)RetSig", args...)`
// dispatches a JNI instance-method call. The lowering hand-emits the
// vtable indirection:
//
// ifs = *env // JNINativeInterface*
// cls = ifs->GetObjectClass(env, target)
// mid = ifs->GetMethodID(env, cls, name, sig)
// ifs->Call<Type>Method(env, target, mid, args...)
//
// Phase 1.17 introduces method-ID caching via static slots populated
// at module-init; this step keeps the per-call-site lookup.
//
// Host can't dlopen libjvm via the JIT, so the JNI body is gated
// behind `inline if OS == .android`. The macOS test path strips the
// body and prints "skipped"; the cross_compile.sh Android target
// verifies that the gated body actually compiles + links against
// libjvm in the Android sysroot.
#import "modules/std.sx";
#import "modules/build.sx";
// Android target requires a `#jni_main` Activity declaration to
// satisfy the entry-point check. An empty stub class is enough — this
// file is testing `#jni_call` lowering, not Activity wiring.
SxJniCallVoidStub :: #jni_main #jni_class("co/swipelab/sxjnicall/SxJniCallVoidStub") { }
main :: () -> i32 {
inline if OS == .android {
// Real Android entry passes env + target via the user's `onCreate`
// / `#jni_attach`. For the cross-compile-only test we just need
// the lowering to emit valid IR; runtime correctness is exercised
// by the chess sx_android_query_safe_insets path.
env : *void = null;
target : *void = null;
#jni_env(env) {
#jni_call(void)(target, "noop", "()V");
}
}
inline if OS != .android {
print("skipped (not android)\n");
}
0
}

View File

@@ -0,0 +1,32 @@
// Phase 1 step 1.16 (PLAN-FFI.md): two `#jni_call` sites against the
// same (class, method, sig) tuple. Today each site emits its own
// `GetObjectClass` + `GetMethodID` call (no caching yet); after 1.17
// the lowering collapses to one cached `jmethodID` per unique
// (class, method, sig).
//
// Runtime: unreachable. The JNI dispatch is all indirect through
// `*env` — no external symbol declarations — so the IR loads cleanly
// into the host JIT, and the dereferences never execute because the
// guarding `g_should_call` global stays false. A plain `if false`
// would get constant-folded, taking the `unused_jni` body out of the
// IR with it; the runtime-readable global keeps the call alive so
// the JNI lowering is visible to the IR snapshot.
#import "modules/std.sx";
g_should_call : bool = false;
unused_jni :: (env: *void, target: *void) {
#jni_env(env) {
#jni_call(void)(target, "noop", "()V");
#jni_call(void)(target, "noop", "()V");
}
}
main :: () -> i32 {
if g_should_call {
unused_jni(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,28 @@
// Phase 1 step 1.18 (PLAN-FFI.md): `#jni_call(i32)` (jint return).
// Today the lowering for any non-void return drops to `LLVMGetUndef`
// — the IR snapshot captures that placeholder. Step 1.18-fix wires
// the `.i32 => 49` (`CallIntMethod`) arm and the snapshot updates to
// show the full GetObjectClass + GetMethodID + CallIntMethod
// sequence (using the cache machinery landed in 1.17).
//
// Runtime: unreachable. Same `g_should_call` global pattern as
// `ffi-jni-call-03` — keeps `read_int` alive so the IR snapshot has
// the JNI lowering visible, but the dereferences never execute.
#import "modules/std.sx";
g_should_call : bool = false;
read_int :: (env: *void, target: *void) -> i32 {
#jni_env(env) {
#jni_call(i32)(target, "getCount", "()I")
}
}
main :: () -> i32 {
if g_should_call {
_ := read_int(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,24 @@
// Phase 1 step 1.19 (PLAN-FFI.md): `#jni_call(i64)` (jlong return).
// Today the non-void switch falls through to `LLVMGetUndef`; the IR
// snapshot captures the placeholder. The make-green commit adds the
// `.i64 => Jni.CallLongMethod` arm and the snapshot updates to show
// the full dispatch through vtable slot 52, reusing the 1.17 slot
// interning.
#import "modules/std.sx";
g_should_call : bool = false;
read_long :: (env: *void, target: *void) -> i64 {
#jni_env(env) {
#jni_call(i64)(target, "currentTimeMillis", "()J")
}
}
main :: () -> i32 {
if g_should_call {
_ := read_long(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,21 @@
// Phase 1 step 1.20 (PLAN-FFI.md): `#jni_call(f64)` (jdouble return).
// First non-integer return type for JNI — exercises the LLVM
// `double` ret path through CallDoubleMethod (vtable slot 58).
#import "modules/std.sx";
g_should_call : bool = false;
read_double :: (env: *void, target: *void) -> f64 {
#jni_env(env) {
#jni_call(f64)(target, "getValue", "()D")
}
}
main :: () -> i32 {
if g_should_call {
_ := read_double(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,23 @@
// Phase 1 step 1.21 (PLAN-FFI.md): `#jni_call(bool)` (jboolean
// return). JNI's `jboolean` is a single-byte unsigned integer (0 or
// 1) on every supported ABI; sx's `bool` lowers to LLVM `i1` but the
// arg/return coercion path widens it to the byte slot the JNI
// runtime expects. CallBooleanMethod lives at vtable slot 37.
#import "modules/std.sx";
g_should_call : bool = false;
read_bool :: (env: *void, target: *void) -> bool {
#jni_env(env) {
#jni_call(bool)(target, "isShown", "()Z")
}
}
main :: () -> i32 {
if g_should_call {
_ := read_bool(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,28 @@
// Phase 1 step 1.22 (PLAN-FFI.md): `#jni_call(*void)` (jobject
// return). Last return-type variant in the matrix. The returned
// jobject is a JNI LocalRef — its lifetime is bounded by the native
// frame, so chains of calls within one frame don't need explicit
// cleanup, but calls that escape (cached cross-frame) should be
// promoted via `NewGlobalRef`. For chess Android use the returned
// jobject is consumed inline by the next `#jni_call`, so no cleanup
// is needed here.
//
// CallObjectMethod lives at vtable slot 34.
#import "modules/std.sx";
g_should_call : bool = false;
get_window :: (env: *void, activity: *void) -> *void {
#jni_env(env) {
#jni_call(*void)(activity, "getWindow", "()Landroid/view/Window;")
}
}
main :: () -> i32 {
if g_should_call {
_ := get_window(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,24 @@
// Phase 1 step 1.23 (PLAN-FFI.md): `#jni_static_call` lowering.
// For static dispatch the `target` arg IS already a `jclass`, so the
// lowering skips `GetObjectClass` and uses `GetStaticMethodID` +
// `CallStatic<Type>Method` instead. Today `is_static = true` drops
// to `LLVMGetUndef`; the snapshot captures the placeholder. Next
// commit wires the static dispatch path.
#import "modules/std.sx";
g_should_call : bool = false;
call_static_max :: (env: *void, cls: *void) -> i32 {
#jni_env(env) {
#jni_static_call(i32)(cls, "max", "(II)I", 3, 7)
}
}
main :: () -> i32 {
if g_should_call {
_ := call_static_max(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,28 @@
// Regression: `#jni_call(f32)` (jfloat return).
// Before the fix, the Call<T>Method switch in `src/ir/emit_llvm.zig`
// only handled `.f64` (jdouble), so any JNI method returning `float`
// fell through to the `else` arm and emitted `LLVMGetUndef` — a
// silent-undef footgun that shipped on Android (chess
// `MotionEvent.getX()` / `getY()` came through as `undef` arguments
// to `sx_android_push_touch`, breaking every touch).
//
// This test exercises the `.f32` slot (CallFloatMethod, vtable 55) +
// proves the build doesn't error out the JNI dispatch path for it.
#import "modules/std.sx";
g_should_call : bool = false;
read_float :: (env: *void, target: *void) -> f32 {
#jni_env(env) {
#jni_call(f32)(target, "getValue", "()F")
}
}
main :: () -> i32 {
if g_should_call {
_ := read_float(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,33 @@
// Regression: when a `#jni_call` method returns a type the
// `Call<T>Method` switch in `emit_llvm.zig` can't dispatch
// (anything outside void/bool/i32/i64/f32/f64/pointer), the
// compiler must emit a DIAGNOSTIC at lower time rather than
// silently producing `LLVMGetUndef` at codegen time. Without
// this guard, the chess Android touch bug shipped: an
// unsupported return type silently became `undef` and showed
// up as garbage arguments downstream.
//
// Here we declare a JNI method returning `i8` (jbyte, not yet
// wired into the call-method switch). The compile must fail
// with a clear message naming the method + return type.
#import "modules/std.sx";
Buf :: #jni_class("java/nio/ByteBuffer") extern {
get :: (self: *Self) -> i8;
}
g_should_call : bool = false;
unused :: (env: *void, b: *Buf) {
#jni_env(env) {
_ := b.get();
}
}
main :: () -> i32 {
if g_should_call {
unused(null, null);
}
0
}

View File

@@ -0,0 +1,18 @@
// Phase 2 step 2.0 (PLAN-FFI.md): xfail parser test for the
// type-introducer `Foo :: #jni_class("java/path/Foo") extern { }` directive.
//
// Today's parser doesn't recognize `#jni_class` as a known hash
// directive after `::`, so it falls through to expression parsing
// and fails at the `#` token. Step 2.1 (next commit) extends
// `parseConstBinding` to accept the directive, treat an empty body
// as an opaque forward declaration, and re-snapshots this file to
// capture the green behavior.
#import "modules/std.sx";
Foo :: #jni_class("java/path/Foo") extern { }
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,20 @@
// Phase 2 step 2.2 (PLAN-FFI.md): xfail then green for the instance
// method body item inside a `#jni_class` declaration.
//
// `name :: (self: *Self, args...) -> Ret;` is the shape — semicolon
// terminated (extern declaration, no body). The 2.1 parser only
// accepts an empty body; step 2.2 extends `parseJniClassDecl` to
// loop over body items and collect method declarations.
#import "modules/std.sx";
// `Self` here refers to the enclosing `View` type — resolved at
// 2.x sema, not at parse time.
View :: #jni_class("android/view/View") extern {
getId :: (self: *Self) -> i32;
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,19 @@
// Phase 2 step 2.3 (PLAN-FFI.md): xfail then green for class/static
// method declarations inside a `#jni_class` body.
//
// Instance vs class method is determined by the first param's TYPE:
// `(self: *Self, ...)` ⇒ instance, anything else (here, `(n: i32)`)
// ⇒ class method, dispatched via `GetStaticMethodID` /
// `CallStatic*` at lowering time (Phase 2.12). No explicit `static`
// keyword; the param shape carries the signal.
#import "modules/std.sx";
Math :: #jni_class("java/lang/Math") extern {
abs :: (n: i32) -> i32; // no `self: *Self` → class method
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,25 @@
// Phase 2 step 2.4 (PLAN-FFI.md): xfail then green for the `#extends`
// and `#implements` body items inside a `#jni_class` declaration.
//
// `#extends Alias;` declares a single-inheritance superclass reference
// using the sx-side alias name (not the runtime Java path — that lives
// in the alias's own `#jni_class(...)` directive arg). `#implements
// Alias;` is repeatable and records interface conformance.
//
// Step 2.4 introduces `hash_extends` and `hash_implements` tokens and
// refactors `JniClassDecl.methods` into a `members` tagged union that
// holds method/extends/implements variants.
#import "modules/std.sx";
View :: #jni_class("android/view/View") extern { }
Window :: #jni_class("android/view/Window") extern {
#extends View;
getDecorView :: (self: *Self) -> *View;
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,19 @@
// Phase 2 step 2.5 (PLAN-FFI.md): xfail then green for the field body
// item inside a `#jni_class` declaration.
//
// `name: Type;` declares an instance field backed by JNI's
// `Get<Type>Field` / `Set<Type>Field` family at lowering time (2.13).
// Step 2.5 extends `parseJniClassDecl` to recognise the colon between
// name and type as the field-decl indicator (vs the `::` of a method).
#import "modules/std.sx";
Point :: #jni_class("android/graphics/Point") extern {
x: i32;
y: i32;
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,20 @@
// Phase 2 step 2.6 (PLAN-FFI.md): xfail then green for the
// `#jni_method_descriptor("(Sig)Ret")` per-method JNI-descriptor
// override.
//
// The default descriptor is derived from sx types (Phase 2.8); this
// directive is the escape hatch when the auto-derived signature
// doesn't match (e.g., synthetic methods, ambiguous overloads,
// accessing JNI internals). It appears trailing the method's return
// type, before the terminating `;`.
#import "modules/std.sx";
Foo :: #jni_class("com/example/Foo") extern {
weirdMethod :: (self: *Self) -> i32 #jni_method_descriptor("()I");
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,47 @@
// Phase 2 step 2.7 (PLAN-FFI.md): xfail then green for the remaining
// six type-introducer directive forms parsing with the same body
// grammar as `#jni_class`. No codegen yet — Phase 3 picks up Obj-C
// codegen, Phase 4 picks up Swift.
//
// The directives this step turns on:
// #jni_interface — Java interface binding
// #objc_class — Obj-C class binding
// #objc_protocol — Obj-C protocol binding
// #swift_class — Swift class binding (via @_cdecl bridge)
// #swift_struct — Swift @frozen value-type binding
// #swift_protocol — Swift @objc-bridgeable protocol binding
//
// Internally the AST collapses into one `runtime_class_decl` node
// carrying a `runtime` discriminator. Today the parser rejects each
// of these because the lexer doesn't recognise the directive name.
#import "modules/std.sx";
IFoo :: #jni_interface("com/example/IFoo") extern {
bar :: (self: *Self) -> i32;
}
NSString :: #objc_class("NSString") extern {
length :: (self: *Self) -> i32;
}
NSCopying :: #objc_protocol("NSCopying") extern {
copy :: (self: *Self) -> *Self;
}
URL :: #swift_class("Foundation.URL") extern {
absoluteString :: (self: *Self) -> *void;
}
Date :: #swift_struct("Foundation.Date") extern {
timeIntervalSince1970 :: (self: *Self) -> f64;
}
Hashable :: #swift_protocol("Swift.Hashable") extern {
hash :: (self: *Self) -> i32;
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1,31 @@
// Phase 2 step 2.11 (PLAN-FFI.md): xfail then green for DSL call-site
// lowering — `inst.method(args)` on a `#jni_class`-typed value lowers
// to `#jni_call(T)(inst, "method", "(sig)Ret", args...)` with the
// descriptor auto-derived from the sx signature.
//
// `#jni_env(env)` brings env into lexical scope; the omitted-env
// `#jni_call` form (2.16b) picks it up directly.
#import "modules/std.sx";
Activity :: #jni_class("android/app/Activity") extern {
getWindow :: (self: *Self) -> *void;
}
g_should_call : bool = false;
unused_jni :: (env: *void, act: *Activity) {
#jni_env(env) {
// Today: this fails — sema doesn't know `Activity` as a type, or
// the method dispatch doesn't recognize runtime-class members.
win := act.getWindow();
}
}
main :: () -> i32 {
if g_should_call {
unused_jni(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,49 @@
// Regression: `obj.method()` runtime-class dispatch with a `float`
// return type used to silently emit `LLVMGetUndef` because the
// `Call<T>Method` switch in `emit_llvm.zig` didn't cover `.f32`.
// Combined with multiple such calls inlined as args to a single
// outer call (`f(o.a(), o.b(), o.c())`), every arg after the first
// went out as `undef` — exactly the chess Android touch failure
// (`MotionEvent.getX()` + `getY()` came through as `undef`s into
// `sx_android_push_touch`).
//
// This test exercises BOTH the `.f32` jdispatch slot AND the
// "multiple runtime-class method calls as args to one outer call"
// pattern. The bodies are gated behind a runtime-false flag so the
// JNI lookups never execute (no JVM in the test runtime), but the
// codegen path still has to emit the calls correctly.
#import "modules/std.sx";
MotionEvent :: #jni_class("android/view/MotionEvent") extern {
getAction :: (self: *Self) -> i32;
getX :: (self: *Self) -> f32;
getY :: (self: *Self) -> f32;
}
sx_consume_touch :: (action: i32, x: f32, y: f32) {
// Black-hole call so the args aren't dead-stripped before LLVM
// verification gets a chance to look at the call site.
if action == 0 and x == 0.0 and y == 0.0 {
print("zero\n");
}
}
g_should_call : bool = false;
drive_touch :: (env: *void, ev: *MotionEvent) {
#jni_env(env) {
// The bug: getX() / getY() lowered to `undef` floats and the
// call to sx_consume_touch passed garbage. Post-fix, all three
// JNI calls emit proper Call<T>Method dispatches.
sx_consume_touch(ev.getAction(), ev.getX(), ev.getY());
}
}
main :: () -> i32 {
if g_should_call {
drive_touch(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,21 @@
// Phase 2 step 2.16a (PLAN-FFI.md): xfail then green for the
// `#jni_env(env) { body }` block-form intrinsic.
//
// Scopes a JNIEnv* over a lexical block. Step 2.16a lands parser +
// AST + sema acceptance — the body runs as a normal block, the env
// arg is captured but not yet used at lowering. The TL push/pop
// semantics (step 2.16b) and lexical-direct resolution in `#jni_call`
// (step 2.16c) follow.
//
// Today the lexer doesn't recognise `#jni_env` and the parser errors
// at the unknown directive token.
#import "modules/std.sx";
main :: () -> i32 {
synth_env : *void = null;
#jni_env(synth_env) {
print("inside #jni_env scope\n");
}
0
}

View File

@@ -0,0 +1,30 @@
// Phase 2 step 2.16b (PLAN-FFI.md): xfail then green for the
// lexical-direct env resolution inside a `#jni_env` scope.
//
// `#jni_call(T)(target, "name", "sig", args...)` — three args before
// the first string literal — has the env omitted. The lowering walks
// the in-progress AST visit stack to find the enclosing `#jni_env(env)`
// block and uses that env value directly as the IR-level env arg.
// No thread-local read, no per-call lookup; env stays register-
// resident across loop bodies (the hot-path optimisation).
//
// Today's lower expects 4+ args and errors when called with 3.
#import "modules/std.sx";
g_should_call : bool = false;
unused_jni :: (env: *void, target: *void) {
#jni_env(env) {
// Omitted env — env comes from the enclosing #jni_env scope.
#jni_call(void)(target, "noop", "()V");
}
}
main :: () -> i32 {
if g_should_call {
unused_jni(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,37 @@
// Phase 2 step 2.16c (PLAN-FFI.md): xfail then green for the
// thread-local env fallback in `#jni_call`. When a `#jni_call` site
// has its env arg omitted AND no `#jni_env` block exists in the same
// function (e.g., we're in a helper called FROM such a block), the
// compiler emits a TL load instead of a sema error.
//
// The TL is pushed/popped by the `#jni_env(env) { ... }` enclosing
// scope at runtime; helpers that don't see the lexical scope still
// pick up the env transparently. Cross-function callers no longer
// need to thread env as an explicit parameter.
//
// Today (2.16b only): lowerJniCall errors when env is omitted and
// jni_env_stack is empty, because TL emission isn't wired yet.
#import "modules/std.sx";
g_should_call : bool = false;
// Helper fn — no `#jni_env` block in scope. Without TL fallback this
// errors because the omitted env can't be resolved.
helper :: (target: *void) {
#jni_call(void)(target, "noop", "()V");
}
unused :: (env: *void, target: *void) {
#jni_env(env) {
helper(target);
}
}
main :: () -> i32 {
if g_should_call {
unused(null, null);
}
print("ok\n");
0
}

View File

@@ -0,0 +1,31 @@
// `#jni_main` pipeline slice 2 (PLAN-FFI.md): the compiler renders a
// `.java` source for a `#jni_main #jni_class("...")` declaration, runs
// `javac` + `d8`, and bundles `classes.dex` into the APK.
//
// Slice 2 only wires the plumbing — the manifest still points at
// `android.app.NativeActivity`, so the user's class isn't loaded at
// runtime. Slice 3 (manifest synthesis) and slice 4 (RegisterNatives)
// land in follow-up commits.
//
// Build to inspect APK contents (requires Android SDK + JDK):
// /Users/agra/projects/sx/zig-out/bin/sx build --target android \
// --apk /tmp/sxjnimain.apk --bundle-id co.swipelab.sxjnimain \
// -o /tmp/libsxjnimain.so examples/ffi-jni-main-01-emit.sx
// unzip -l /tmp/sxjnimain.apk | grep classes.dex
#import "modules/std.sx";
#import "modules/build.sx";
// `*Bundle` resolves through the class registry to `android.os.Bundle`
// in the emitted Java — needed for `onCreate`'s @Override to match
// NativeActivity's superclass signature.
Bundle :: #jni_class("android/os/Bundle") extern { }
// `#jni_main` flags this as the launchable Android Activity class. The
// `onCreate` body is empty for now — slice 4 wires `RegisterNatives`
// so the `sx_onCreate` native delegate actually binds to a sx-side fn.
SxApp :: #jni_main #jni_class("co/swipelab/sxjnimain/SxApp") {
onCreate :: (self: *Self, b: *Bundle) { }
}
main :: () -> i32 { 0 }

View File

@@ -0,0 +1,24 @@
// `super.method(args)` dispatch inside a `#jni_main` Activity body
// (chess-on-Pixel migration, R.6). The override of a lifecycle method
// like `onCreate` needs to invoke the parent's `onCreate` so the
// Android runtime's setup completes (`SuperNotCalledException`
// otherwise) — the sx-side body calls `super.onCreate(b)` and the
// compiler lowers it to JNI `CallNonvirtualVoidMethod` against the
// parent class declared via `#extends`.
//
// No `#extends` here → defaults to `android.app.Activity`. The smoke
// is compile-only — runtime correctness is verified by APK install
// + on-device launch, which is the chess deploy.
#import "modules/std.sx";
#import "modules/build.sx";
Bundle :: #jni_class("android/os/Bundle") extern { }
SxApp :: #jni_main #jni_class("co/swipelab/sxjnimainsuper/SxApp") {
onCreate :: (self: *Self, b: *Bundle) {
super.onCreate(b);
}
}
main :: () -> i32 { 0 }

View File

@@ -0,0 +1,30 @@
// `Alias.new(args)` constructor dispatch on a `extern #jni_class`
// (chess-on-Pixel migration, R.6). The sx-side `static new :: (...) ->
// *Self;` member lowers to JNI `FindClass + GetMethodID("<init>", sig)
// + NewObject(env, clazz, mid, args...)`.
//
// This smoke instantiates a `SurfaceView` from inside the Activity's
// `onCreate` body — chess's render surface starts the same way.
#import "modules/std.sx";
#import "modules/build.sx";
Bundle :: #jni_class("android/os/Bundle") extern { }
JContext :: #jni_class("android/content/Context") extern { }
SurfaceView :: #jni_class("android/view/SurfaceView") extern {
new :: (ctx: *JContext) -> *Self; // no `self: *Self` → class method
}
g_held_view : *void = null;
SxApp :: #jni_main #jni_class("co/swipelab/sxjnictor/SxApp") {
onCreate :: (self: *Self, b: *Bundle) {
super.onCreate(b);
ctx : *JContext = xx self; // Activity IS a JContext (extends JContext).
view := SurfaceView.new(ctx);
g_held_view = xx view; // keep alive so LLVM doesn't DCE the construction.
}
}
main :: () -> i32 { 0 }

View File

@@ -0,0 +1,15 @@
// Phase 3 (FFI-linkage) — postfix `extern` on a `#jni_class` aggregate, the
// new spelling of the legacy prefix `#jni_class(…) extern` import. Parse-only
// on macOS (no JVM at runtime), mirroring 1412's runtime-class reference.
// View :: #jni_class("…") extern { … } == View :: #jni_class(…) extern("…") { … }
#import "modules/std.sx";
View :: #jni_class("android/view/View") extern {
getId :: (self: *Self) -> i32;
}
main :: () -> i32 {
print("parse-only ok\n");
0
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
skipped (not android)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
0

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
ok

View File

@@ -0,0 +1 @@
0

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
ok

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
0

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
ok

View File

@@ -0,0 +1,5 @@
error: JNI method 'Buf.get' returns 'i8', which isn't supported by the JNI call-method lowering yet — only void/bool/i32/i64/f32/f64 and pointers are wired up
--> examples/ffi-jni/1410-ffi-jni-call-11-unsupported-return-diag.sx:24:14
|
24 | _ := b.get();
| ^^^^^

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
parse-only ok

View File

@@ -0,0 +1 @@
0

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
ok

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1 @@
inside #jni_env scope

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More