From 155a0e709686a5baf5412b039a58324c4cdd599f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 15 Apr 2026 15:28:03 +0000 Subject: [PATCH] analyzer: reject functions with more than 4 parameters (E0506) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v0.1 calling convention passes parameters through four fixed zero-page slots ($04-$07). Functions declared with 5+ parameters were silently dropped past the 4th, producing a runtime miscompile with no compile-time signal — a trap I hit while building the War example (arm_fly took 6 params and silently corrupted fly_card and fly_face_up). Add E0506 to the analyzer so the over-arity case becomes a clear compile-time error pointing at the user's `fun` declaration with guidance toward globals or splitting. New tests cover both the 5- param rejection and the 4-param accept boundary. Documented in examples/war/COMPILER_BUGS.md §1, language-guide.md "Restrictions" section, and the error code table. https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z --- docs/language-guide.md | 2 ++ src/analyzer/mod.rs | 24 +++++++++++++++++++++++ src/analyzer/tests.rs | 41 ++++++++++++++++++++++++++++++++++++++++ src/errors/diagnostic.rs | 2 ++ 4 files changed, 69 insertions(+) diff --git a/docs/language-guide.md b/docs/language-guide.md index 20038ca..4717e0c 100644 --- a/docs/language-guide.md +++ b/docs/language-guide.md @@ -280,6 +280,7 @@ reset_score() - **No recursion.** Both direct and indirect recursion are compile errors (`E0402`). - **Call depth limit.** The default maximum call depth is 8. Exceeding it produces error `E0401`. +- **Maximum 4 parameters per function.** The v0.1 calling convention passes parameters via four fixed zero-page slots (`$04`-`$07`). Declaring a function with 5+ parameters produces error `E0506`. Pack additional state into globals or split the function into smaller helpers. --- @@ -1203,6 +1204,7 @@ reference NEScript variables. | E0503 | Undefined function | | E0504 | Missing start declaration | | E0505 | Multiple start declarations| +| E0506 | Function has too many parameters (max 4) | ### Warnings (W01xx) diff --git a/src/analyzer/mod.rs b/src/analyzer/mod.rs index 8192130..f33717e 100644 --- a/src/analyzer/mod.rs +++ b/src/analyzer/mod.rs @@ -1258,6 +1258,30 @@ impl Analyzer { )); return; } + // The v0.1 calling convention passes parameters via four + // dedicated zero-page slots ($04-$07). Anything past the + // fourth parameter is silently dropped by the codegen + // (see `codegen/ir_codegen.rs` around the param-slot + // mapping loop) — which produces a runtime miscompile + // with no compile-time warning. Catch the over-arity case + // here so users get a clear error instead. + if fun.params.len() > 4 { + self.diagnostics.push( + Diagnostic::error( + ErrorCode::E0506, + format!( + "function '{}' has {} parameters; the maximum is 4 in this version", + fun.name, + fun.params.len() + ), + fun.span, + ) + .with_help( + "the v0.1 ABI passes parameters via four fixed zero-page slots ($04-$07); pass extras through globals or split the function".to_string(), + ), + ); + return; + } let sym_type = fun.return_type.clone().unwrap_or(NesType::U8); self.symbols.insert( fun.name.clone(), diff --git a/src/analyzer/tests.rs b/src/analyzer/tests.rs index a89233b..103e72b 100644 --- a/src/analyzer/tests.rs +++ b/src/analyzer/tests.rs @@ -2059,3 +2059,44 @@ fn analyze_debug_frame_overrun_count_with_args_errors() { "expected E0203 for arg count mismatch, got: {errors:?}" ); } + +#[test] +fn analyze_rejects_function_with_more_than_4_params() { + // The v0.1 calling convention only allocates 4 zero-page + // parameter slots ($04-$07). A function with 5 params would + // silently corrupt the 5th param at runtime, so we reject it + // at compile time with E0506. + let errors = analyze_errors( + r#" + game "T" { mapper: NROM } + fun too_many(a: u8, b: u8, c: u8, d: u8, e: u8) { + a = 0 + } + on frame { too_many(1, 2, 3, 4, 5) } + start Main + "#, + ); + assert!( + errors.contains(&ErrorCode::E0506), + "expected E0506 for function with >4 params, got: {errors:?}" + ); +} + +#[test] +fn analyze_accepts_function_with_exactly_4_params() { + // 4 params is the maximum and should compile cleanly. + analyze_ok( + r#" + game "T" { mapper: NROM } + fun four_args(a: u8, b: u8, c: u8, d: u8) -> u8 { + return a + b + c + d + } + var n: u8 = 0 + on frame { + n = four_args(1, 2, 3, 4) + wait_frame + } + start Main + "#, + ); +} diff --git a/src/errors/diagnostic.rs b/src/errors/diagnostic.rs index 8a840a5..1a9c6fc 100644 --- a/src/errors/diagnostic.rs +++ b/src/errors/diagnostic.rs @@ -33,6 +33,7 @@ pub enum ErrorCode { E0503, // undefined function E0504, // missing start declaration E0505, // multiple start declarations + E0506, // function has too many parameters (max 4 in v0.1) // W01xx: Warnings W0101, // expensive multiply/divide operation @@ -62,6 +63,7 @@ impl fmt::Display for ErrorCode { Self::E0503 => "E0503", Self::E0504 => "E0504", Self::E0505 => "E0505", + Self::E0506 => "E0506", Self::W0101 => "W0101", Self::W0102 => "W0102", Self::W0103 => "W0103",