mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 08:55:38 +00:00
Analyzer: validate return statement types
Tracks the current function's declared return type and checks that `return value` statements match it. Returning from a void function with a value produces E0203; returning a value with the wrong type produces E0201. State handler `return value` is still silently accepted (the value is discarded). https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
parent
28e1627953
commit
6f0e4a0a74
2 changed files with 74 additions and 3 deletions
|
|
@ -50,6 +50,8 @@ pub fn analyze(program: &Program) -> AnalysisResult {
|
|||
in_loop: false,
|
||||
used_vars: HashSet::new(),
|
||||
function_signatures: HashMap::new(),
|
||||
current_return_type: None,
|
||||
in_function_body: false,
|
||||
};
|
||||
analyzer.analyze_program(program);
|
||||
|
||||
|
|
@ -78,6 +80,15 @@ struct Analyzer {
|
|||
/// Function name to parameter types (in order). Used to validate
|
||||
/// call arity and argument types.
|
||||
function_signatures: HashMap<String, Vec<NesType>>,
|
||||
/// Return type of the function currently being analyzed, or None
|
||||
/// when the function has no declared return type. Only meaningful
|
||||
/// when `in_function_body` is true.
|
||||
current_return_type: Option<NesType>,
|
||||
/// True while analyzing a function body (as opposed to a state
|
||||
/// handler's `on_enter` / `on_exit` / `on_frame` block). Used to
|
||||
/// distinguish "void function" from "state handler" when checking
|
||||
/// `return value` statements.
|
||||
in_function_body: bool,
|
||||
}
|
||||
|
||||
impl Analyzer {
|
||||
|
|
@ -153,7 +164,11 @@ impl Analyzer {
|
|||
}
|
||||
self.mark_var_used(¶m.name);
|
||||
}
|
||||
self.current_return_type.clone_from(&fun.return_type);
|
||||
self.in_function_body = true;
|
||||
self.check_block(&fun.body, &state_names);
|
||||
self.current_return_type = None;
|
||||
self.in_function_body = false;
|
||||
for name in &added_params {
|
||||
self.symbols.remove(name);
|
||||
}
|
||||
|
|
@ -571,10 +586,22 @@ impl Analyzer {
|
|||
self.check_expr_type(frame, &NesType::U8);
|
||||
}
|
||||
}
|
||||
Statement::Return(Some(expr), _) => {
|
||||
// For M1, just validate the expression without checking return type
|
||||
Statement::Return(Some(expr), span) => {
|
||||
self.walk_expr_reads(expr);
|
||||
let _ = self.infer_type(expr);
|
||||
if let Some(ret_ty) = self.current_return_type.clone() {
|
||||
// Function with declared return type — check the value.
|
||||
self.check_expr_type(expr, &ret_ty);
|
||||
} else if self.in_function_body {
|
||||
// Function with no declared return type ("void"),
|
||||
// but the return statement has a value.
|
||||
self.diagnostics.push(Diagnostic::error(
|
||||
ErrorCode::E0203,
|
||||
"return value in function with no declared return type",
|
||||
*span,
|
||||
));
|
||||
}
|
||||
// State handlers (`in_function_body == false`) accept
|
||||
// `return value` silently — the value is simply discarded.
|
||||
}
|
||||
Statement::Call(name, args, span) => {
|
||||
if self.symbols.contains_key(name) {
|
||||
|
|
|
|||
|
|
@ -270,6 +270,50 @@ fn analyze_call_arity_in_expr_context() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_return_type_ok() {
|
||||
analyze_ok(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
fun get_five() -> u8 { return 5 }
|
||||
on frame { wait_frame }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_return_wrong_type() {
|
||||
let errors = analyze_errors(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
fun is_ok() -> bool { return 5 }
|
||||
on frame { wait_frame }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(
|
||||
errors.contains(&ErrorCode::E0201),
|
||||
"returning wrong type should produce E0201, got: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_return_value_from_void_fn() {
|
||||
let errors = analyze_errors(
|
||||
r#"
|
||||
game "Test" { mapper: NROM }
|
||||
fun do_nothing() { return 5 }
|
||||
on frame { wait_frame }
|
||||
start Main
|
||||
"#,
|
||||
);
|
||||
assert!(
|
||||
errors.contains(&ErrorCode::E0203),
|
||||
"returning value from void function should produce E0203, got: {errors:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn analyze_const_assignment_error() {
|
||||
let errors = analyze_errors(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue