ERR/E4.2: entry-point signature gate for main
Add validateMainSignature (lowerRoot Pass 4a). main must take no parameters
and have a single-slot return — void, an integer (POSIX exit code), or `-> !`
/ `-> !Named` (the error tag rides the single return register, which the JIT's
`() -> i32` main call handles directly). Other shapes are now clean
diagnostics instead of silent miscompiles:
- `main :: () -> string` previously SEGFAULTED (the i32 return register was
read as a string) — now a clear "return type must be void, an integer, or
`!`" error.
- `main :: (x: ...)` previously ran silently (param ignored) — now rejected.
- `main :: () -> f64` / non-failable tuple / etc. — rejected.
The value-carrying failable `-> (T, !)` is rejected for now: its multi-slot
{value, error} return ABI-mismatches the entry-point call and segfaults. That
shape needs the E4.2 entry-point wrapper (gated on E3 return traces); rejecting
loudly beats miscompiling. `-> !` (no value) IS accepted — single-slot, works
today (success exits 0; a raise exits nonzero, trace/tag story pending E3).
examples/239-main-signature-reject.sx covers the `-> string` rejection (exit 1).
Accepted shapes are exercised elsewhere (238 for integer-exit truncation; the
existing suite for void/int main). Gates: zig build, zig build test, bash
tests/run_examples.sh (276 passed; lone failure is the user's uncommitted
213-canonical-map pack WIP).
This commit is contained in:
@@ -343,6 +343,8 @@ pub const Lowering = struct {
|
||||
self.lowerDeferredTypeFns();
|
||||
// Pass 4: target-specific entry-point sanity checks
|
||||
self.checkRequiredEntryPoints();
|
||||
// Pass 4a: validate main's signature (ERR E4.2 entry-point gate).
|
||||
self.validateMainSignature();
|
||||
// Pass 4b: eagerly lower bodied methods on sx-defined `#objc_class`
|
||||
// declarations. The Obj-C runtime calls these via IMP pointers
|
||||
// registered in M1.2 A.4 — no sx-side call path drives lazy
|
||||
@@ -360,6 +362,43 @@ pub const Lowering = struct {
|
||||
self.synthesizeJniMainStubs();
|
||||
}
|
||||
|
||||
/// ERR E4.2: the entry-point signature gate. `main` must take no parameters
|
||||
/// and have a SINGLE-slot return: void (`()` / `-> ()` / `-> void`), an
|
||||
/// integer (POSIX exit code, truncated to u8), or `-> !` / `-> !Named` (the
|
||||
/// error tag rides the single return register). The multi-slot
|
||||
/// `-> (T, !)` tuple return is NOT yet supported — the JIT calls main as
|
||||
/// `() -> i32`, so a 2-slot `{value, error}` return ABI-mismatches and
|
||||
/// segfaults; that shape lands with the E4.2 entry-point wrapper. Any other
|
||||
/// shape (`-> string`, `-> f64`, a non-failable tuple, …) is a clean
|
||||
/// diagnostic rather than a silent miscompile.
|
||||
fn validateMainSignature(self: *Lowering) void {
|
||||
const fd = self.fn_ast_map.get("main") orelse return;
|
||||
|
||||
if (fd.params.len != 0) {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, fd.params[0].name_span, "main: parameters must be empty; return type must be void, an integer, or `!`", .{});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const rt = self.resolveReturnType(fd);
|
||||
// Single-slot returns the JIT's `() -> i32` ABI handles directly:
|
||||
// void / integer, and a pure failable `-> !` (a bare u32 error tag).
|
||||
if (rt == .void or self.isIntEx(rt)) return;
|
||||
if (self.errorChannelOf(rt)) |chan| {
|
||||
if (rt == chan) return; // pure `-> !` / `-> !Named`
|
||||
// `-> (T..., !)` — a multi-slot tuple return; not yet wired.
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, if (fd.return_type) |rtn| rtn.span else null, "a value-carrying failable `main` (`-> (T, !)`) is not yet supported — its multi-slot return ABI-mismatches the entry-point call; use `-> !` (no value) or a non-failable integer return, or absorb errors with `catch`", .{});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, if (fd.return_type) |rtn| rtn.span else null, "main: return type must be void, an integer, or `!`; got '{s}'", .{self.formatTypeName(rt)});
|
||||
}
|
||||
}
|
||||
|
||||
/// On Android, the OS loads the .so via a Java-side Activity declared
|
||||
/// with `#jni_main #jni_class("...")`. The Java class drives the
|
||||
/// lifecycle (onCreate / onPause / etc.) and sx provides the native
|
||||
|
||||
Reference in New Issue
Block a user