1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 00:45:38 +00:00

analyzer: reject functions with more than 4 parameters (E0506)

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
This commit is contained in:
Claude 2026-04-15 15:28:03 +00:00
parent 8ababdcec4
commit 155a0e7096
No known key found for this signature in database
4 changed files with 69 additions and 0 deletions

View file

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

View file

@ -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(),

View file

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

View file

@ -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",