mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55: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:
parent
8ababdcec4
commit
155a0e7096
4 changed files with 69 additions and 0 deletions
|
|
@ -280,6 +280,7 @@ reset_score()
|
||||||
|
|
||||||
- **No recursion.** Both direct and indirect recursion are compile errors (`E0402`).
|
- **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`.
|
- **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 |
|
| E0503 | Undefined function |
|
||||||
| E0504 | Missing start declaration |
|
| E0504 | Missing start declaration |
|
||||||
| E0505 | Multiple start declarations|
|
| E0505 | Multiple start declarations|
|
||||||
|
| E0506 | Function has too many parameters (max 4) |
|
||||||
|
|
||||||
### Warnings (W01xx)
|
### Warnings (W01xx)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1258,6 +1258,30 @@ impl Analyzer {
|
||||||
));
|
));
|
||||||
return;
|
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);
|
let sym_type = fun.return_type.clone().unwrap_or(NesType::U8);
|
||||||
self.symbols.insert(
|
self.symbols.insert(
|
||||||
fun.name.clone(),
|
fun.name.clone(),
|
||||||
|
|
|
||||||
|
|
@ -2059,3 +2059,44 @@ fn analyze_debug_frame_overrun_count_with_args_errors() {
|
||||||
"expected E0203 for arg count mismatch, got: {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
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ pub enum ErrorCode {
|
||||||
E0503, // undefined function
|
E0503, // undefined function
|
||||||
E0504, // missing start declaration
|
E0504, // missing start declaration
|
||||||
E0505, // multiple start declarations
|
E0505, // multiple start declarations
|
||||||
|
E0506, // function has too many parameters (max 4 in v0.1)
|
||||||
|
|
||||||
// W01xx: Warnings
|
// W01xx: Warnings
|
||||||
W0101, // expensive multiply/divide operation
|
W0101, // expensive multiply/divide operation
|
||||||
|
|
@ -62,6 +63,7 @@ impl fmt::Display for ErrorCode {
|
||||||
Self::E0503 => "E0503",
|
Self::E0503 => "E0503",
|
||||||
Self::E0504 => "E0504",
|
Self::E0504 => "E0504",
|
||||||
Self::E0505 => "E0505",
|
Self::E0505 => "E0505",
|
||||||
|
Self::E0506 => "E0506",
|
||||||
Self::W0101 => "W0101",
|
Self::W0101 => "W0101",
|
||||||
Self::W0102 => "W0102",
|
Self::W0102 => "W0102",
|
||||||
Self::W0103 => "W0103",
|
Self::W0103 => "W0103",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue