From 13c25d452646eb0576187d185833be0b8d1543af Mon Sep 17 00:00:00 2001 From: ChadSec Date: Tue, 14 Jul 2026 22:58:06 +0300 Subject: [PATCH 01/15] CI: Increase tarpaulin timeout --- .github/workflows/unit_tests_with_coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/unit_tests_with_coverage.yml b/.github/workflows/unit_tests_with_coverage.yml index ea36e94..17e0586 100644 --- a/.github/workflows/unit_tests_with_coverage.yml +++ b/.github/workflows/unit_tests_with_coverage.yml @@ -40,7 +40,7 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' - name: Run unit tests and generate tarpaulin report - run: cargo tarpaulin --timeout 30000 --verbose --lib --out Xml --output-dir ./coverage + run: cargo tarpaulin --timeout 80000 --verbose --lib --out Xml --output-dir ./coverage - name: Upload to Codacy From ffb80cf7a176cd78a98a90e190bfa7debba3420d Mon Sep 17 00:00:00 2001 From: ChadSec Date: Tue, 14 Jul 2026 22:58:26 +0300 Subject: [PATCH 02/15] docs: Fix typos in README --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index aca76be..d274aea 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,13 @@ # Work-in-progress This bootstrap compiler implements parser, semantic analysis and enforcement, and the transpiler. -It still lacks: structs and methods, enums, sin (unsafe) blocks. +It still lacks: structs and methods, enums, danger blocks. + +**NOTE**: **This project is just a hobby language** with design objective of being the safest and most readable systems programming language, but ***there is no legal guarantees that comes with using this project*** + +**NOTE**: This compiler is a *phase 1* bootstrap compiler, it's not meant for developing production software, ***it's only meant for bootstrapping the language*** + + # Compiling the bootstrap compiler. **Note**: The latest commit in main branch is always the latest stable release. @@ -47,13 +53,6 @@ The compiler binary will be located in `target/release/goldlang`. Feel free to m That will compile a GoldLang file, and produce a binary at `TARGET_BINARY_PATH`. -**NOTE**: **This project is just a hobby language** with design objective of being the safest and most readable systems programming language! -**There is no guarantees that comes with using this project, I am just 1 person making this programming language for myself, and I've decided to open-source it for others, because I was looking for a language just like this, and I couldn't find, so I decided to make it.** - -**NOTE**: **This compiler is a phase 1 bootstrap compiler, it's not meant for systems development. it's only meant for bootstrapping the language** - - - # Example syntax ``` # This is a comment From 8b880c63c056a41a5415dc7e3c171c3fd057396c Mon Sep 17 00:00:00 2001 From: ChadSec Date: Tue, 14 Jul 2026 23:29:30 +0300 Subject: [PATCH 03/15] fix: Fix issue that wouldve allowed overshadowing in for statements and multi declarations --- src/semantic.rs | 51 +++++++++-------------------------------- src/semantic/helpers.rs | 31 ++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/semantic.rs b/src/semantic.rs index 7367432..c5700af 100644 --- a/src/semantic.rs +++ b/src/semantic.rs @@ -259,30 +259,16 @@ fn check_stmts( check_const(cons, locals, fun_sigs)?; }, Stmt::VarDecl(var) => { - if fun_sigs.contains_key(&var.name) { - return Err(GoldError::Semantic(format!( - "Variable identifier name `{}` is already taken by a function. (line {} column {})", - var.name, var.span.line, var.span.column - ))) - } + helpers::check_identifier_is_already_taken(&var.name, &var.span, locals, fun_sigs)?; let expr_ty = infer::infer_expr_type(&mut var.value, locals, fun_sigs, Some(var.type_name.clone()))?; if expr_ty != var.type_name { return Err(GoldError::Semantic(format!( "Type mismatch assigning to `{}`: got `{}`, expected `{}` (line {} column {})", var.name, expr_ty, var.type_name, var.span.line, var.span.column - ))); - } - - // GoldLang commandment 1. You shall not overshadow variables - if locals.contains_key(&var.name) { - return Err(GoldError::Semantic(format!( - "Variable `{}` is already declared, overshadowing is not allowed. (line {} column {})", - var.name, var.span.line, var.span.column - ))) + ))) } - let mut value_len: Option = None; // Check if source value is a variable and if its locked or moved, and moves it @@ -336,7 +322,7 @@ fn check_stmts( } } ); - } + }, Stmt::VarDeclMulti(var_list, call_expr) => { // Expect the right-hand side to be a function Call expression @@ -359,14 +345,7 @@ fn check_stmts( ))); } - - // GoldLang commandment 1. You shall not overshadow variables - if locals.contains_key(&var.name) { - return Err(GoldError::Semantic(format!( - "Variable `{}` is already declared, overshadowing is not allowed. (line {} column {})", - var.name, var.span.line, var.span.column - ))) - } + helpers::check_identifier_is_already_taken(&var.name, &var.span, locals, fun_sigs)?; // insert into locals // @@ -387,7 +366,7 @@ fn check_stmts( return Err(GoldError::Semantic(format!( "Multi-declarement requires only a single function call on the right-hand side (line {} column {})", stmt_span.line, stmt_span.column - ))); + ))) } } @@ -408,7 +387,7 @@ fn check_stmts( return Err(GoldError::Semantic(format!( "Type mismatch assigning to `{}`: got `{}`, expected `{}` (line {} column {})", assign.name, expr_ty, varinfo.ty, assign.span.line, assign.span.column - ))); + ))) } @@ -752,8 +731,7 @@ fn check_stmts( } } } - } - + }, Stmt::For(for_stmt) => { let expr_ty = infer::infer_expr_type(&mut for_stmt.value, locals, fun_sigs, None)?; @@ -762,17 +740,11 @@ fn check_stmts( return Err(GoldError::Semantic(format!( "For loop statement require an expression to be evaulatable to any `Array` type, or `range(expr1, expr2)`, instead we got `{}` (line {} column {})", expr_ty, stmt_span.line, stmt_span.column, - ))); - } - - - if locals.contains_key(&for_stmt.holder_name) { - return Err(GoldError::Semantic(format!( - "Cannot use variable name `{}` in for loop statement as it is already declared. (line {} column {})", - for_stmt.holder_name, stmt_span.line, stmt_span.column, ))) } + helpers::check_identifier_is_already_taken(&for_stmt.holder_name, &stmt_span, locals, fun_sigs)?; + // If this is a for looping over an array, move the array only if its not an array // literal. i.e. its a variable that holds an array. if expr_ty.is_array_type() @@ -846,15 +818,14 @@ fn check_stmts( upstream.push(var_name.clone()); } - // We also add holder_name to the list to prevent programmer overshadowing the + // We also add holder_name to the upstream list to prevent programmer overshadowing the // variable within the loop. upstream.push(for_stmt.holder_name.clone()); check_stmts(func, &mut for_stmt.branch, &mut locals_clone, upstream.as_slice(), fun_sigs, true)?; update_local_assignments_from_clone(locals, locals_clone); - } - + }, Stmt::While(while_stmt) => { let expr_ty = infer::infer_expr_type(&mut while_stmt.condition, locals, fun_sigs, Some(Type::Bool))?; diff --git a/src/semantic/helpers.rs b/src/semantic/helpers.rs index e278655..a29a23f 100644 --- a/src/semantic/helpers.rs +++ b/src/semantic/helpers.rs @@ -3,7 +3,10 @@ use super::{ Stmt, Span, Expr, - GoldError + GoldError, + + BindingInfo, + HashMap }; use crate::ast::{ @@ -62,6 +65,32 @@ pub fn get_bigger_type_of_two_integers(t_1: Type, t_2: Type) -> Type { +pub fn check_identifier_is_already_taken( + identifier: &str, + span: &Span, + locals: &mut HashMap, + fun_sigs: &HashMap, Option>)> +) -> Result<(), GoldError> { + if fun_sigs.contains_key(identifier) { + return Err(GoldError::Semantic(format!( + "Variable identifier name `{}` is already taken by a function. (line {} column {})", + identifier, span.line, span.column + ))) + } + + if locals.contains_key(identifier) { + return Err(GoldError::Semantic(format!( + "Variable `{}` is already declared, overshadowing is not allowed. (line {} column {})", + identifier, span.line, span.column + ))) + } + + Ok(()) +} + + + + // helper to get the span of a statement (so we can point to offending code) pub fn stmt_span(s: &Stmt) -> Span { From 71b68d86d614ca4714cf88ece752148ee3b831e4 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Wed, 15 Jul 2026 18:07:46 +0300 Subject: [PATCH 04/15] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d274aea..4f6c1e3 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ - Documentation is forced for functions, structs, and constants. - No type inference, everything must be explicilty stated (this prevents common errors that stems especially from numeric types and assumptions.). - No overshadowing allowed. Making codebases easier to audit, and reducing likelyhood of logic bugs. -- `lock` and `unlock` statements allow you to declare "zones" where variables behave as constants. +- `lock` and `unlock` statements allow you to declare "zones" where variables behave as constants, protecting you from yourself . **GoldLang**'s bootstrap compiler transpiles down to pure Rust for a mathematical guarantee of safety: `"If Rust is safe, then GoldLang must also be at least as safe as Rust"`. From 5f87f64fbe8bf76c98ed1f2e69f1e7ddf4f2ff52 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Wed, 15 Jul 2026 18:08:14 +0300 Subject: [PATCH 05/15] refactor: General code clean-up --- src/semantic.rs | 89 +++++++++++++++++++------------------------------ 1 file changed, 34 insertions(+), 55 deletions(-) diff --git a/src/semantic.rs b/src/semantic.rs index c5700af..a19a5e7 100644 --- a/src/semantic.rs +++ b/src/semantic.rs @@ -179,7 +179,7 @@ fn check_global_stmt( match globalstmt { GlobalStmt::Const(cons) => check_const(cons, storage, fun_sigs), - GlobalStmt::_PlaceholderDummyUntilIAddMoreStmtsHereLikeStructsAndEnums => panic!("(Compiler bug) Unimplemented") + GlobalStmt::_PlaceholderDummyUntilIAddMoreStmtsHereLikeStructsAndEnums => todo!() } } @@ -273,9 +273,7 @@ fn check_stmts( // Check if source value is a variable and if its locked or moved, and moves it if let Expr::Var { name: src_name, span } = &var.value { - let src = locals.get_mut(src_name).unwrap_or_else(|| panic!( - "(Compiler bug) infer_expr_type should've already errored if source variable didnt exist, but it didnt. var: {var:?}" - )); + let src = locals.get_mut(src_name).unwrap(); match &mut src.kind { BindingKind::Var { moved: src_moved, len: src_len, .. } => { @@ -422,9 +420,7 @@ fn check_stmts( let mut value_len: Option = None; if let Expr::Var { name: src_name, span } = &assign.value { - let src = locals.get_mut(src_name).unwrap_or_else(|| panic!( - "(Compiler bug) infer_expr_type should've already errored if source variable didnt exist, but it didnt. assign: {assign:?}" - )); + let src = locals.get_mut(src_name).unwrap(); match &mut src.kind { BindingKind::Var { moved: src_moved, len: src_len, .. } => { @@ -466,7 +462,7 @@ fn check_stmts( match &mut varinfo.kind { BindingKind::Var { len, .. } => *len = value_len, - BindingKind::Const { .. } => panic!("(Compiler bug) Variable is const, despite our supposed earlier checks that its not. Wtf ?: {varinfo:?}") + _ => unreachable!() } }, @@ -592,9 +588,7 @@ fn check_stmts( for var_name in var_names_to_lock { - let var = locals.get_mut(&var_name).unwrap_or_else(|| { - panic!("(Compiler bug) Variable doesnt exist in locals despite our earlier call to infer_expr_type shouldve checked the variable thourghly, including its existence, but apparently it didnt. expr_vec: `{expr_vec:?}`, var_name: {var_name:?}"); - }); + let var = locals.get_mut(&var_name).unwrap(); match &mut var.kind { BindingKind::Var { locked, .. } => { @@ -670,9 +664,7 @@ fn check_stmts( for var_name in var_names_to_unlock { - let var = locals.get_mut(&var_name).unwrap_or_else(|| { - panic!("(Compiler bug) Variable doesnt exist in locals despite our earlier call to infer_expr_type shouldve checked the variable thourghly, including its existence, but apparently it didnt. expr_vec: `{expr_vec:?}`, var_name: {var_name:?}") - }); + let var = locals.get_mut(&var_name).unwrap(); match &mut var.kind { BindingKind::Var { locked, .. } => { @@ -749,9 +741,7 @@ fn check_stmts( // literal. i.e. its a variable that holds an array. if expr_ty.is_array_type() && let Expr::Var { name, .. } = &for_stmt.value { - let src = locals.get_mut(name).unwrap_or_else(|| panic!( - "(Compiler bug) infer_expr_type should've already errored if the array variable didnt exist, but it didnt. for_stmt: {for_stmt:?}" - )); + let src = locals.get_mut(name).unwrap(); match &mut src.kind { BindingKind::Var { moved: src_moved, .. } => { @@ -770,29 +760,25 @@ fn check_stmts( let mut locals_clone = locals.clone(); - // We inject the holder variable into the locals. It is "fake" variable that does - // not exist in the AST, but we need it in locals to make analysis work. - // - - let decided_ty: Type; + let local_fake_var_ty: Type = match expr_ty { + Type::Array(inner_ty) | + Type::FixedArray(inner_ty, _) => *inner_ty, - if let Type::Array(inner_ty) = expr_ty { - decided_ty = *inner_ty; + // Since earlier we checked to see if for stmt is array type, or a rangecall + // expression, we know rangecall evaluates to integer, so, this means + // rangecall. + // + other if other.is_integer_type() => other, - } else if let Type::FixedArray(inner_ty, _) = expr_ty { - decided_ty = *inner_ty; - - } else if expr_ty.is_integer_type() { - decided_ty = expr_ty; - } else { - panic!( - "(Compiler bug) Expected for loop expression to either be an array or an integer (more precisely an integer cuz programmer used range()), instead we got: {:?} {:?}", - for_stmt.value, expr_ty); - } + _ => unreachable!() + }; + // We inject the holder variable into the locals. It is "fake" variable that does + // not exist in the AST, but we need it in locals to make analysis work. + // - // NOTE only specific if decided_ty is of an array: + // NOTE that's only relevant if `local_fake_var_ty` is of an array type: // Out-of-bounds access protection here is non-existent, but thats fine because Rust // will catch at transpile layer // However it would be nicer if we can do better job of catching out of bounds @@ -801,7 +787,7 @@ fn check_stmts( locals_clone.insert( for_stmt.holder_name.clone(), BindingInfo { - ty: decided_ty, + ty: local_fake_var_ty, kind: BindingKind::Var { value: None, moved: false, @@ -822,19 +808,18 @@ fn check_stmts( // variable within the loop. upstream.push(for_stmt.holder_name.clone()); - check_stmts(func, &mut for_stmt.branch, &mut locals_clone, upstream.as_slice(), fun_sigs, true)?; update_local_assignments_from_clone(locals, locals_clone); }, Stmt::While(while_stmt) => { - let expr_ty = infer::infer_expr_type(&mut while_stmt.condition, locals, fun_sigs, Some(Type::Bool))?; + let expr_ty = infer::infer_expr_type(&mut while_stmt.condition, locals, fun_sigs, None).unwrap(); if expr_ty != Type::Bool { return Err(GoldError::Semantic(format!( "While statement require an expression to be evaulatable to type `bool`, instead we got `{}` (line {} column {})", - expr_ty, stmt_span.line, stmt_span.column, - ))); + expr_ty, stmt_span.line, stmt_span.column + ))) } // This gets all upstream variable names, and passes it to check stmts to ensure @@ -848,8 +833,7 @@ fn check_stmts( check_stmts(func, &mut while_stmt.branch, &mut locals_clone, upstream.as_slice(), fun_sigs, true)?; update_local_assignments_from_clone(locals, locals_clone); - } - + }, Stmt::Infinite(infinite_stmt) => { // This gets all upstream variable names, and passes it to check stmts to ensure // you cannot overshadow them. @@ -862,9 +846,7 @@ fn check_stmts( check_stmts(func, &mut infinite_stmt.branch, &mut locals_clone, upstream.as_slice(), fun_sigs, true)?; update_local_assignments_from_clone(locals, locals_clone); - } - - + }, Stmt::Break(break_stmt) => { if !in_loop { @@ -886,13 +868,13 @@ fn check_stmts( } Stmt::If(if_stmt) => { - let main_expr_ty = infer::infer_expr_type(&mut if_stmt.condition, locals, fun_sigs, Some(Type::Bool))?; + let main_expr_ty = infer::infer_expr_type(&mut if_stmt.condition, locals, fun_sigs, None).unwrap(); if main_expr_ty != Type::Bool { return Err(GoldError::Semantic(format!( "If statement require an expression to be evaulatable to type `bool`, instead we got `{}` (line {} column {})", - main_expr_ty, stmt_span.line, stmt_span.column, - ))); + main_expr_ty, stmt_span.line, stmt_span.column + ))) } // This gets all upstream variable names, and passes it to check stmts to ensure @@ -906,7 +888,6 @@ fn check_stmts( // state. let locals_clone = locals.clone(); - let mut main_locals_clone = locals.clone(); let mut else_locals_clone = locals.clone(); @@ -915,13 +896,13 @@ fn check_stmts( for s in &mut if_stmt.elif_branches { - let elif_expr_ty = infer::infer_expr_type(&mut s.0, locals, fun_sigs, Some(Type::Bool))?; + let elif_expr_ty = infer::infer_expr_type(&mut s.0, locals, fun_sigs, None).unwrap(); if elif_expr_ty != Type::Bool { return Err(GoldError::Semantic(format!( "Elif statements require an expression to be evaulatable to type `bool`, instead we got `{}` (line {} column {})", - elif_expr_ty, stmt_span.line, stmt_span.column, - ))); + elif_expr_ty, stmt_span.line, stmt_span.column + ))) } @@ -1011,9 +992,7 @@ fn check_call( // If this arg is a variable, mark it moved (same semantics as before) if let Expr::Var { name: vname, span: _ } = arg_expr { - let v = locals.get_mut(vname).unwrap_or_else(|| panic!( - "(Compiler bug) infer_expr_type should've already errored if source argument variable didnt exist, but it didnt. arg_expr: {arg_expr:?}" - )); + let v = locals.get_mut(vname).unwrap(); match &mut v.kind { BindingKind::Var { moved, ..} => { From 098ca38e6721d96377a18f31778917f743e5904b Mon Sep 17 00:00:00 2001 From: ChadSec Date: Wed, 15 Jul 2026 18:08:41 +0300 Subject: [PATCH 06/15] refactor: General code clean-up --- src/semantic/branch_analysis.rs | 42 +++++++++++++-------------------- src/semantic/helpers.rs | 5 ---- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/semantic/branch_analysis.rs b/src/semantic/branch_analysis.rs index 1ea7248..8e591ad 100644 --- a/src/semantic/branch_analysis.rs +++ b/src/semantic/branch_analysis.rs @@ -21,8 +21,7 @@ pub fn dead_code_analysis(block: &Vec, in_loop: bool) -> Result, in_loop: bool) -> Result, in_loop: bool) -> Result { let body = &while_stmt.branch; if body.is_empty() { return Err(GoldError::Semantic(format!( "While loop branch has no statements. Empty branches are not allowed (line {} column {})", - while_stmt.span.line, while_stmt.span.column, - ))); - + while_stmt.span.line, while_stmt.span.column + ))) } - dead_code_analysis(body, in_loop)?; }, @@ -67,12 +62,10 @@ pub fn dead_code_analysis(block: &Vec, in_loop: bool) -> Result, in_loop: bool) -> Result, in_loop: bool) -> Result, in_loop: bool) -> Result {}, Stmt::Infinite(infinite_stmt) => { @@ -179,8 +171,8 @@ pub fn return_branch_analysis( Stmt::Break(break_stmt) => { return Err(GoldError::Semantic(format!( "You cannot `break` out of a infinite loop if its the last statement in a function that returns. Use a return statement instead. (line {} column {})", - break_stmt.span.line, break_stmt.span.column, - ))); + break_stmt.span.line, break_stmt.span.column + ))) } Stmt::If(_) => { diff --git a/src/semantic/helpers.rs b/src/semantic/helpers.rs index a29a23f..79172af 100644 --- a/src/semantic/helpers.rs +++ b/src/semantic/helpers.rs @@ -63,8 +63,6 @@ pub fn get_bigger_type_of_two_integers(t_1: Type, t_2: Type) -> Type { } - - pub fn check_identifier_is_already_taken( identifier: &str, span: &Span, @@ -89,9 +87,6 @@ pub fn check_identifier_is_already_taken( } - - - // helper to get the span of a statement (so we can point to offending code) pub fn stmt_span(s: &Stmt) -> Span { match s { From 6c87a6bb6e865c5c373a242eb99fcdb0f02f2b7b Mon Sep 17 00:00:00 2001 From: ChadSec Date: Mon, 27 Jul 2026 16:06:37 +0300 Subject: [PATCH 07/15] refactor: Fix unreachable branch analysis --- src/semantic.rs | 36 +---- src/semantic/branch_analysis.rs | 273 ++++++++++++++++++++++++++++++-- 2 files changed, 265 insertions(+), 44 deletions(-) diff --git a/src/semantic.rs b/src/semantic.rs index a19a5e7..ca3608c 100644 --- a/src/semantic.rs +++ b/src/semantic.rs @@ -132,38 +132,10 @@ fn check_function( check_stmts(&func.clone(), &mut func.body, locals, &upstream_var_names, fun_sigs, false)?; - - // Branch analysis to determine if function returns in all branches - // - - // This is just to check that function has at least one statement - // Reason it's here and not in dead_code_snalysis is because so - // dead code analysis can properly error with lines. - // - let last_func_stmt = func.body.last(); - if last_func_stmt.is_none() { - return Err(GoldError::Semantic(format!( - "Function `{}` has no statements, empty functions are not allowed! (line {} column {})", - func.name, func.span.line, func.span.column, - ))); - } - - // We call dead code analysis here after check_stmts, because we want checked semantics. - // Semantics take priority more than dead code - // - branch_analysis::dead_code_analysis(&func.body, false)?; - - // Return analysis only needs to check last statement which has return statements - // because dead code analysis should not let dead code pass. - // last statement should be always be the one actualy always returning. + // code analysis ensures no empty branches, nor dead code, and also ensures correct returning + // branches. // - // We only do return branch analysis if function has declared return type. - if func.return_type.is_some() { - branch_analysis::return_branch_analysis(func, last_func_stmt.unwrap(), false, false)?; - } - - Ok(()) - + branch_analysis::code_analysis(&func) } @@ -704,7 +676,7 @@ fn check_stmts( Some(declared_ty_vec) => { if declared_ty_vec.len() != expr_vec.len() { return Err(GoldError::Semantic(format!( - "Return length mismatch in `{}`: got `{}` expressions, expected `{}` expressions (line {} column {})", + "Return length mismatch in `{}`: got {} expressions, expected {} expressions (line {} column {})", func.name, expr_vec.len(), declared_ty_vec.len(), stmt_span.line, stmt_span.column, ))) } diff --git a/src/semantic/branch_analysis.rs b/src/semantic/branch_analysis.rs index 8e591ad..4ad4fa5 100644 --- a/src/semantic/branch_analysis.rs +++ b/src/semantic/branch_analysis.rs @@ -1,10 +1,263 @@ +/// This file is mainly responsible for branch analysis, such as: +/// 1. Analyzing functions for empty branchaes and erroring. +/// +/// and +/// 2. Analyzing return branches to ensure branches correctly return, or infinitely loops +/// without breaking +/// +/// and +/// 3. Analyzing branches for unreachable code (i.e. statements after a `return`, or a `break` statements) +/// +/// use super::{ - Stmt, + Stmt, + // Type, GoldError, helpers, Function }; + +/// Performs code analysis on a specific function, the analysis include: +/// - empty branch analysis (ensuring the function body, and statements bodies, are not empty) +/// - return branch analysis (ensuring proper returning and legal control flow in branches) +/// +/// +pub fn code_analysis( + func: &Function +) -> Result<(), GoldError> { + + /* + let last_func_stmt = func.body.last().ok_or(GoldError::Semantic(format!( + "Function `{}` has no statements, empty functions are not allowed! (line {} column {})", + func.name, func.span.line, func.span.column + )))?; +*/ + + empty_branch_analysis_hazmat(&func.body)?; + + unreachable_code_branch_analysis_hazmat(&func.body)?; + + /*if let Some(return_type) = &func.return_type { + return_branch_analysis_hazmat(return_type, &func.body)?; + }*/ + + Ok(()) +} + + +/// Checks statements in a statement block for empty branches. +/// NOTE: Do not call this function directly. this function only meant to be called within +/// `code_analysis` +fn empty_branch_analysis_hazmat( + block: &Vec +) -> Result<(), GoldError> { + for stmt in block { + match stmt { + Stmt::Return(_) |Stmt::Break(_) | Stmt::Continue(_) | Stmt::Lock(_) | Stmt::Unlock(_) | Stmt::Expr(_) | Stmt::VarDecl(_) + | Stmt::VarDeclMulti(_, _) | Stmt::VarAssign(_) | Stmt::VarAssignMulti(_) | Stmt::Const(_) => {}, + + Stmt::Infinite(infinite_stmt) => { + let body = &infinite_stmt.branch; + if body.is_empty() { + return Err(GoldError::Semantic(format!( + "Infinite loop branch has no statements. Empty branches are not allowed (line {} column {})", + infinite_stmt.span.line, infinite_stmt.span.column + ))) + } + + empty_branch_analysis_hazmat(body)?; + }, + Stmt::While(while_stmt) => { + let body = &while_stmt.branch; + if body.is_empty() { + return Err(GoldError::Semantic(format!( + "While loop branch has no statements. Empty branches are not allowed (line {} column {})", + while_stmt.span.line, while_stmt.span.column + ))) + } + + empty_branch_analysis_hazmat(body)?; + }, + Stmt::For(for_stmt) => { + let body = &for_stmt.branch; + if body.is_empty() { + return Err(GoldError::Semantic(format!( + "For loop branch has no statements. Empty branches are not allowed (line {} column {})", + for_stmt.span.line, for_stmt.span.column + ))) + } + + empty_branch_analysis_hazmat(body)?; + }, + Stmt::If(if_stmt) => { + if if_stmt.if_branch.is_empty() { + return Err(GoldError::Semantic(format!( + "If statement main branch has no statements. Empty branches are not allowed (line {} column {})", + if_stmt.span.line, if_stmt.span.column + ))) + } + + empty_branch_analysis_hazmat(&if_stmt.if_branch)?; + + for s_vec in &if_stmt.elif_branches { + let expr_span = helpers::expr_span(&s_vec.0); + + if s_vec.1.is_empty() { + return Err(GoldError::Semantic(format!( + "If statement `elif` branch has no statements. Empty branches are not allowed (line {} column {})", + expr_span.line, expr_span.column + ))) + } + + empty_branch_analysis_hazmat(&s_vec.1)?; + } + + if let Some(else_branch) = &if_stmt.else_branch { + if else_branch.is_empty() { + return Err(GoldError::Semantic(format!( + "If statement `else` branch has no statements. Empty branches are not allowed (line {} column {})", + if_stmt.span.line, if_stmt.span.column + ))) + } + + empty_branch_analysis_hazmat(else_branch)?; + } + } + } + + } + + Ok(()) +} + + +/// Performs unreachable code branch analysis, errors if it detects statements and or branches that could never be +/// "reached" (aka executed) due to `return`s and `break`s (or `continue`) statements. +/// +/// NOTE: Do not call this function directly. this function only meant to be called within `code_analysis` function +/// +fn unreachable_code_branch_analysis_hazmat( + block: &Vec +) -> Result<(bool, bool), GoldError> { + let mut certain_return_detected: bool = false; + let mut stop_detected: bool = false; + + for stmt in block { + if certain_return_detected || stop_detected { + let current_stmt_span = helpers::stmt_span(stmt); + let last_block_stmt_span = helpers::stmt_span(block.last().unwrap()); + + if current_stmt_span == last_block_stmt_span { + return Err(GoldError::Semantic(format!( + "Unreachable statement at line {}", + current_stmt_span.line + ))) + } else { + return Err(GoldError::Semantic(format!( + "Unreachable code starting from line {} down to line {}", + current_stmt_span.line, last_block_stmt_span.line + ))) + } + } + + match stmt { + Stmt::Return(_) => certain_return_detected = true, + Stmt::Break(_) | Stmt::Continue(_) => stop_detected = true, + Stmt::Infinite(inf_stmt) => (certain_return_detected, _) = unreachable_code_branch_analysis_hazmat(&inf_stmt.branch)?, + Stmt::If(if_stmt) => if let Some(else_branch) = &if_stmt.else_branch { + let (if_branch_returns, if_branch_stops) = unreachable_code_branch_analysis_hazmat(&if_stmt.if_branch)?; + let (else_branch_returns, else_branch_stops) = unreachable_code_branch_analysis_hazmat(&else_branch)?; + + let (mut elif_branches_returns, mut elif_branches_stops) = (true, true); + + for s_vec in &if_stmt.elif_branches { + let (elif_returns, elif_stops) = unreachable_code_branch_analysis_hazmat(&s_vec.1)?; + + if !elif_returns { + elif_branches_returns = false; + } + + if !elif_stops { + elif_branches_stops = false; + } + } + + certain_return_detected = if_branch_returns && elif_branches_returns && else_branch_returns; + + // If a specific branch returns, it might as well act as a stop (break or + // continue) because whatever after it, is for sure unreachable. :) + // + stop_detected = (if_branch_returns || if_branch_stops) && (elif_branches_returns || elif_branches_stops) && (else_branch_returns || else_branch_stops); + }, + + Stmt::While(while_stmt) => (_, _) = unreachable_code_branch_analysis_hazmat(&while_stmt.branch)?, + + // Non branching statements, so we safely ignore them + Stmt::Const(_) | Stmt::Expr(_) | Stmt::VarDecl(_) | Stmt::VarDeclMulti(..) | Stmt::VarAssign(_) | Stmt::VarAssignMulti(..) | + Stmt::Lock(_) | Stmt::Unlock(_) | Stmt::For(_) => {} + } + + } + + Ok((certain_return_detected, stop_detected)) +} + +/* +/// Performs return branch analysis code branch analysis, errors if it detects statements that could never be +/// reached due to `returns` and `breaks` statements. +/// +/// NOTE: Do not call this function directly. this function only meant to be called within `code_analysis` function +/// +fn return_branch_analysis_hazmat( + block: &Vec +) -> Result { + let mut certain_return_detected: bool = false; + let mut break_detected: bool = false; + + for stmt in block { + if certain_return_detected || break_detected { + let current_stmt_span = helpers::stmt_span(stmt); + let last_block_stmt_span = helpers::stmt_span(block.last().unwrap()); + + if current_stmt_span == last_block_stmt_span { + return Err(GoldError::Semantic(format!( + "Unreachable statement at line `{}`", + current_stmt_span.line + ))) + } else { + return Err(GoldError::Semantic(format!( + "Unreachable code starting from line `{}` up to line `{}`", + current_stmt_span.line, last_block_stmt_span.line + ))) + } + } + + match stmt { + Stmt::Return(_) => certain_return_detected = true, + Stmt::Break(_) => break_detected = true, + Stmt::Infinite(inf_stmt) => certain_return_detected = unreachable_code_branch_analysis_hazmat(&inf_stmt.branch)?, + Stmt::If(if_stmt) => if let Some(else_branch) = &if_stmt.else_branch { + let if_branch_returns = unreachable_code_branch_analysis_hazmat(&if_stmt.if_branch)?; + let else_branch_returns = unreachable_code_branch_analysis_hazmat(&else_branch)?; + + certain_return_detected = if_branch_returns && else_branch_returns; + }, + + // Everything else is ignored. + _ => {} + } + } + + Ok(certain_return_detected) +} +*/ + + + +// pub fn return_branch_analysis(block: &Vec, break_detected: bool, return_detected: bool) -> Result { + +/* pub fn dead_code_analysis(block: &Vec, in_loop: bool) -> Result { // Instead of returning error here, we panic, because if we returned an error here // we would not have ability to pinpoint to the empty branch line. leaving responsiblity to @@ -136,15 +389,16 @@ pub fn return_branch_analysis( match last_stmt { Stmt::Break(break_stmt) => { - // Just a compiler bug guard. + // Just a compiler bug guard to enforce the invariant. assert!(is_loop, "(Compiler bug) check_stmts shouldve errored before we even got called. We got a break statement when we arent even in a loop!"); if forbid_break { return Err(GoldError::Semantic(format!( "You cannot `break` out of a infinite loop if its the last statement in a function that returns. Use a return statement instead. (line {} column {})", - break_stmt.span.line, break_stmt.span.column, + break_stmt.span.line, break_stmt.span.column ))) } + }, Stmt::Return(_) => {}, Stmt::Infinite(infinite_stmt) => { @@ -173,18 +427,13 @@ pub fn return_branch_analysis( "You cannot `break` out of a infinite loop if its the last statement in a function that returns. Use a return statement instead. (line {} column {})", break_stmt.span.line, break_stmt.span.column ))) - } - + }, Stmt::If(_) => { return_branch_analysis(func, s, true, true)?; - } - - + }, Stmt::While(_) | Stmt::For(_) | Stmt::Infinite(_) => { return_branch_analysis(func, s, true, false)?; - } - - + }, // Skip all other statements _ => {} @@ -287,4 +536,4 @@ pub fn return_branch_analysis( Ok(()) } - +*/ From dee347b08e3859a19e7e6bb9c70237d9f0b7377f Mon Sep 17 00:00:00 2001 From: ChadSec Date: Mon, 27 Jul 2026 16:07:15 +0300 Subject: [PATCH 08/15] tests: clean up unit tests for semantics layer --- src/semantic/blackbox_tests/for_stmt_tests.rs | 159 +++++++++++++----- src/semantic/blackbox_tests/var_decl_tests.rs | 24 +-- .../blackbox_tests/var_multi_decl_tests.rs | 42 +++++ 3 files changed, 167 insertions(+), 58 deletions(-) diff --git a/src/semantic/blackbox_tests/for_stmt_tests.rs b/src/semantic/blackbox_tests/for_stmt_tests.rs index dd85350..3d3ecfe 100644 --- a/src/semantic/blackbox_tests/for_stmt_tests.rs +++ b/src/semantic/blackbox_tests/for_stmt_tests.rs @@ -5,7 +5,7 @@ mod for_stmt_tests { use super::*; #[test] - fn for_stmts_value_is_var() { + fn for_stmt_value_is_var() { let literals = get_all_literals(); for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { @@ -37,7 +37,7 @@ mod for_stmt_tests { } #[test] - fn for_stmts_value_is_array_literal() { + fn for_stmt_value_is_array_literal() { let literals = get_all_literals(); for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { @@ -65,7 +65,7 @@ mod for_stmt_tests { } #[test] - fn for_stmts_with_fixed_arrays() { + fn for_stmt_with_fixed_arrays() { let literals = get_all_literals(); for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { @@ -99,7 +99,7 @@ mod for_stmt_tests { // Test for statements with rangecall, with only integer literals, no variables. #[test] - fn for_stmts_with_range_int_literals() { + fn for_stmt_with_range_int_literals() { let literals = get_all_literals_no_arr_str_bool_float(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -130,7 +130,7 @@ mod for_stmt_tests { #[test] - fn for_stmts_with_range_non_int_literals_errors() { + fn for_stmt_with_range_non_int_literals_errors() { let literals_no_ints = get_all_literals_no_arr_no_ints(); for (l, t) in literals_no_ints.iter().zip(ALL_TYPES_NO_INTS_NO_ARR.iter()) { @@ -162,7 +162,7 @@ mod for_stmt_tests { #[test] - fn for_stmts_with_range_mixed_literals_errors() { + fn for_stmt_with_range_mixed_literals_errors() { let literals_no_ints = get_all_literals_no_arr_no_ints(); let literals = get_all_literals_no_arr(); @@ -226,10 +226,79 @@ mod for_stmt_tests { } + #[test] + fn for_stmt_holder_name_already_taken_by_var_errors() { + let literals = get_all_literals(); + + for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { + for i in 0..=100 { + let elements = vec![l.clone(); i]; + + let arr_lit = array_lit(elements.clone(), Some(t.clone())); + + let body = vec![ + var_decl(true, "a", Type::Array(Box::new(t.clone())), arr_lit.clone()), + var_decl(true, "x", Type::Array(Box::new(t.clone())), arr_lit), + Stmt::For(ForStmt{ + holder_name: "x".to_string(), + value: var_expr("a"), + branch: vec![ + // Just dummy declaration, so we don't get flagged by dead code because + // of empty branch. + var_decl(true, "z", t.clone(), l.clone()), + ], + span: span(), + }), + ]; + let func = void_func("foo", vec![], body); + let mut ast = ast_one(func); + let result = check_semantics(&mut ast); + + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("is already declared")); + } + } + } + + #[test] + fn for_stmt_holder_name_already_taken_by_func_errors() { + let literals = get_all_literals(); + + for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { + for i in 0..=100 { + let elements = vec![l.clone(); i]; + + let arr_lit = array_lit(elements.clone(), Some(t.clone())); + + let body = vec![ + var_decl(true, "a", Type::Array(Box::new(t.clone())), arr_lit.clone()), + Stmt::For(ForStmt{ + holder_name: "pair".to_string(), + value: var_expr("a"), + branch: vec![ + // Just dummy declaration, so we don't get flagged by dead code because + // of empty branch. + var_decl(true, "z", t.clone(), l.clone()), + ], + span: span(), + }), + ]; + + let pair_body = vec![return_stmt(vec![l.clone()])]; + let pair = returning_func("pair", vec![], vec![t.clone()], pair_body); + + let main = void_func("foo", vec![], body); + let mut ast = AST { functions: vec![main, pair], globals: vec![] }; + let result = check_semantics(&mut ast); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("is already taken by a function")); + } + } + } #[test] - fn for_stmts_with_range_holder_name_is_already_taken_errors() { + fn for_stmt_with_range_holder_name_is_already_taken_by_var_errors() { let literals = get_all_literals_no_arr_str_bool_float(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -256,58 +325,57 @@ mod for_stmt_tests { let result = check_semantics(&mut ast); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Cannot use variable name `x` in for loop statement as it is already declared")); + assert!(result.unwrap_err().to_string().contains("is already declared")); } } - #[test] - fn for_stmts_with_fixed_array_holder_name_is_already_taken_errors() { - let literals = get_all_literals(); - - for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { - for i in 0..=100 { - let elements = vec![l.clone(); i]; - - let arr_lit = array_lit(elements.clone(), Some(t.clone())); + fn for_stmt_with_range_holder_name_is_already_taken_by_func_errors() { + let literals = get_all_literals_no_arr_str_bool_float(); - let body = vec![ - var_decl(true, "a", Type::FixedArray(Box::new(t.clone()), FixedArraySize::Literal(i)), arr_lit), - var_decl(true, "x", t.clone(), l.clone()), - Stmt::For(ForStmt{ - holder_name: "x".to_string(), - value: var_expr("a"), + for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { + let body = vec![ + Stmt::For(ForStmt{ + holder_name: "pair".to_string(), + value: Expr::RangeCall{ + start: Box::new(l.clone()), + end: Box::new(l.clone()), + span: span() + }, + + branch: vec![ + // Just dummy declaration, so we don't get flagged by dead code because + // of empty branch. + var_decl(true, "z", t.clone(), l.clone()), + ], + span: span(), + }), + ]; + let pair_body = vec![return_stmt(vec![l.clone()])]; + let pair = returning_func("pair", vec![], vec![t.clone()], pair_body); - branch: vec![ - // Just dummy declaration, so we don't get flagged by dead code because - // of empty branch. - var_decl(true, "z", t.clone(), l.clone()), - ], - span: span(), - }), - ]; - let func = void_func("foo", vec![], body); - let mut ast = ast_one(func); - let result = check_semantics(&mut ast); + let main = void_func("foo", vec![], body); + let mut ast = AST { functions: vec![main, pair], globals: vec![] }; + let result = check_semantics(&mut ast); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Cannot use variable name `x` in for loop statement as it is already declared")); - } + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("is already taken by a function")); } } + #[test] - fn for_stmts_with_dyn_array_holder_name_is_already_taken_errors() { - let literals = get_all_literals_no_arr(); + fn for_stmt_with_fixed_array_holder_name_is_already_taken_by_var_errors() { + let literals = get_all_literals(); - for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { + for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { for i in 0..=100 { let elements = vec![l.clone(); i]; let arr_lit = array_lit(elements.clone(), Some(t.clone())); let body = vec![ - var_decl(true, "a", Type::Array(Box::new(t.clone())), arr_lit), + var_decl(true, "a", Type::FixedArray(Box::new(t.clone()), FixedArraySize::Literal(i)), arr_lit), var_decl(true, "x", t.clone(), l.clone()), Stmt::For(ForStmt{ holder_name: "x".to_string(), @@ -326,14 +394,13 @@ mod for_stmt_tests { let result = check_semantics(&mut ast); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Cannot use variable name `x` in for loop statement as it is already declared")); + assert!(result.unwrap_err().to_string().contains("is already declared")); } } } - #[test] - fn for_stmts_with_no_array_no_range() { + fn for_stmt_with_no_array_no_range() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) @@ -361,7 +428,7 @@ mod for_stmt_tests { #[test] - fn for_stmts_fixed_arr_empty_branch_errors() { + fn for_stmt_fixed_arr_empty_branch_errors() { let literals = get_all_literals(); for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { @@ -391,7 +458,7 @@ mod for_stmt_tests { } #[test] - fn for_stmts_dyn_arr_empty_branch_errors() { + fn for_stmt_dyn_arr_empty_branch_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { diff --git a/src/semantic/blackbox_tests/var_decl_tests.rs b/src/semantic/blackbox_tests/var_decl_tests.rs index 149f896..9f458e2 100644 --- a/src/semantic/blackbox_tests/var_decl_tests.rs +++ b/src/semantic/blackbox_tests/var_decl_tests.rs @@ -76,7 +76,7 @@ mod var_decl_tests { #[test] - fn test_var_decl_type_mismatch_errors() { + fn var_decl_type_mismatch_errors() { let literals_no_ints = get_all_literals_no_arr_no_ints(); for t in ALL_INT_TYPES_NO_ARR { @@ -98,7 +98,7 @@ mod var_decl_tests { // #[test] - fn test_vardecl_overshadowing_upstream_var_in_for_loop_holder_errors() { + fn vardecl_overshadowing_upstream_var_in_for_loop_holder_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -123,14 +123,14 @@ mod var_decl_tests { let result = check_semantics(&mut ast); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Cannot use variable name `x` in for loop statement as it is already declared")); + assert!(result.unwrap_err().to_string().contains("is already declared")); } } #[test] - fn test_vardecl_overshadowing_var_in_for_loop_errors() { + fn vardecl_overshadowing_var_in_for_loop_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -160,7 +160,7 @@ mod var_decl_tests { #[test] - fn test_vardecl_overshadowing_var_in_while_loop_errors() { + fn vardecl_overshadowing_var_in_while_loop_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -184,7 +184,7 @@ mod var_decl_tests { } #[test] - fn test_vardecl_overshadowing_var_in_infinite_loop_errors() { + fn vardecl_overshadowing_var_in_infinite_loop_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -209,7 +209,7 @@ mod var_decl_tests { #[test] - fn test_vardecl_overshadowing_var_in_if_main_branch_errors() { + fn vardecl_overshadowing_var_in_if_main_branch_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -236,7 +236,7 @@ mod var_decl_tests { #[test] - fn test_vardecl_overshadowing_var_in_if_else_branch_errors() { + fn vardecl_overshadowing_var_in_if_else_branch_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -268,7 +268,7 @@ mod var_decl_tests { #[test] - fn test_vardecl_overshadowing_var_in_if_elif_branch_errors() { + fn vardecl_overshadowing_var_in_if_elif_branch_errors() { let literals = get_all_literals_no_arr(); for (l, t) in literals.iter().zip(ALL_TYPES_NO_ARR.iter()) { @@ -301,7 +301,7 @@ mod var_decl_tests { // This tests integers / floats only, against Bool / String #[test] - fn test_vardecl_type_mismatch_int_bool_errors() { + fn vardecl_type_mismatch_int_bool_errors() { let literals_ints_floats = get_all_literals_no_arr_str_bool(); @@ -330,7 +330,7 @@ mod var_decl_tests { } #[test] - fn test_use_of_undeclared_variable_other_errors() { + fn use_of_undeclared_variable_other_errors() { // Try referencing non-existent variable "y" for t in ALL_TYPES_NO_ARR { let body = vec![var_decl(true, "x", t.clone(), var_expr("y"))]; // y not declared @@ -343,7 +343,7 @@ mod var_decl_tests { } #[test] - fn test_use_of_undeclared_variable_ourself_errors() { + fn use_of_undeclared_variable_ourself_errors() { // Try referencing non-existent variable "x" aka ourselves. for t in ALL_TYPES_NO_ARR { let body = vec![var_decl(true, "x", t.clone(), var_expr("x"))]; // x not declared diff --git a/src/semantic/blackbox_tests/var_multi_decl_tests.rs b/src/semantic/blackbox_tests/var_multi_decl_tests.rs index bbc1f45..8a93884 100644 --- a/src/semantic/blackbox_tests/var_multi_decl_tests.rs +++ b/src/semantic/blackbox_tests/var_multi_decl_tests.rs @@ -18,6 +18,48 @@ mod var_multi_decl_tests { } } + #[test] + fn var_name_taken_by_same_func_errors() { + let literals = get_all_literals(); + + for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { + let pair_body = vec![return_stmt(vec![l.clone()])]; + let pair = returning_func("pair", vec![], vec![t.clone()], pair_body); + + let vars = vec![ + MultiVariableDeclaration { name: "main".to_string(), type_name: t.clone(), span: span() }, + ]; + let body = vec![Stmt::VarDeclMulti(vars, call_expr("pair", vec![]))]; + let main = void_func("main", vec![], body); + + let mut ast = AST { functions: vec![main, pair], globals: vec![] }; + let result = check_semantics(&mut ast); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("is already taken by a function")); + } + } + + #[test] + fn var_name_taken_by_different_func_errors() { + let literals = get_all_literals(); + + for (l, t) in literals.iter().zip(ALL_TYPES_WITH_DYN_ARR.iter()) { + let pair_body = vec![return_stmt(vec![l.clone()])]; + let pair = returning_func("pair", vec![], vec![t.clone()], pair_body); + + let vars = vec![ + MultiVariableDeclaration { name: "pair".to_string(), type_name: t.clone(), span: span() }, + ]; + let body = vec![Stmt::VarDeclMulti(vars, call_expr("pair", vec![]))]; + let main = void_func("main", vec![], body); + + let mut ast = AST { functions: vec![main, pair], globals: vec![] }; + let result = check_semantics(&mut ast); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("is already taken by a function")); + } + } + #[test] fn unknown_func_call_in_func_call_arg_errors() { let literals = get_all_literals(); From 5a64007e9b1c768263eb31d4ac7a990846247595 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sat, 1 Aug 2026 19:19:46 +0300 Subject: [PATCH 09/15] refactor: Completely rewrite branch analysis (return, unreachable, and empty analysis) --- src/semantic/branch_analysis.rs | 408 ++++++-------------------------- 1 file changed, 76 insertions(+), 332 deletions(-) diff --git a/src/semantic/branch_analysis.rs b/src/semantic/branch_analysis.rs index 4ad4fa5..ee22bdf 100644 --- a/src/semantic/branch_analysis.rs +++ b/src/semantic/branch_analysis.rs @@ -2,45 +2,46 @@ /// 1. Analyzing functions for empty branchaes and erroring. /// /// and -/// 2. Analyzing return branches to ensure branches correctly return, or infinitely loops -/// without breaking +/// 2. Analyzing branches for unreachable code (i.e. statements after a `return`, or a `break` statements) /// /// and -/// 3. Analyzing branches for unreachable code (i.e. statements after a `return`, or a `break` statements) -/// +/// 3. Analyzing return branches to ensure branches correctly return, or infinitely loops +/// without breaking /// use super::{ Stmt, - // Type, GoldError, helpers, Function }; +#[cfg(test)] +mod branch_analysis_tests; + + /// Performs code analysis on a specific function, the analysis include: /// - empty branch analysis (ensuring the function body, and statements bodies, are not empty) -/// - return branch analysis (ensuring proper returning and legal control flow in branches) -/// +/// - unreachable code branch analysis (ensuring function body, and statements bodies, do not +/// contain unreachable statements and or branches) +/// - return branch analysis (ensuring functions with return signature always certainy returns regardless of branch) /// pub fn code_analysis( func: &Function ) -> Result<(), GoldError> { - - /* - let last_func_stmt = func.body.last().ok_or(GoldError::Semantic(format!( + if func.body.is_empty() { + return Err(GoldError::Semantic(format!( "Function `{}` has no statements, empty functions are not allowed! (line {} column {})", func.name, func.span.line, func.span.column - )))?; -*/ - + ))) + } empty_branch_analysis_hazmat(&func.body)?; unreachable_code_branch_analysis_hazmat(&func.body)?; - /*if let Some(return_type) = &func.return_type { - return_branch_analysis_hazmat(return_type, &func.body)?; - }*/ + if func.return_type.is_some() { + return_branch_analysis_hazmat_wrapper(&func)?; + } Ok(()) } @@ -52,6 +53,8 @@ pub fn code_analysis( fn empty_branch_analysis_hazmat( block: &Vec ) -> Result<(), GoldError> { + assert!(!block.is_empty(), "(Compiler bug) Got an empty block of statements fed to `empty_branch_analysis_hazmat`."); + for stmt in block { match stmt { Stmt::Return(_) |Stmt::Break(_) | Stmt::Continue(_) | Stmt::Lock(_) | Stmt::Unlock(_) | Stmt::Expr(_) | Stmt::VarDecl(_) @@ -141,10 +144,10 @@ fn unreachable_code_branch_analysis_hazmat( block: &Vec ) -> Result<(bool, bool), GoldError> { let mut certain_return_detected: bool = false; - let mut stop_detected: bool = false; + let mut certain_stop_detected: bool = false; for stmt in block { - if certain_return_detected || stop_detected { + if certain_return_detected || certain_stop_detected { let current_stmt_span = helpers::stmt_span(stmt); let last_block_stmt_span = helpers::stmt_span(block.last().unwrap()); @@ -163,7 +166,7 @@ fn unreachable_code_branch_analysis_hazmat( match stmt { Stmt::Return(_) => certain_return_detected = true, - Stmt::Break(_) | Stmt::Continue(_) => stop_detected = true, + Stmt::Break(_) | Stmt::Continue(_) => certain_stop_detected = true, Stmt::Infinite(inf_stmt) => (certain_return_detected, _) = unreachable_code_branch_analysis_hazmat(&inf_stmt.branch)?, Stmt::If(if_stmt) => if let Some(else_branch) = &if_stmt.else_branch { let (if_branch_returns, if_branch_stops) = unreachable_code_branch_analysis_hazmat(&if_stmt.if_branch)?; @@ -188,352 +191,93 @@ fn unreachable_code_branch_analysis_hazmat( // If a specific branch returns, it might as well act as a stop (break or // continue) because whatever after it, is for sure unreachable. :) // - stop_detected = (if_branch_returns || if_branch_stops) && (elif_branches_returns || elif_branches_stops) && (else_branch_returns || else_branch_stops); + certain_stop_detected = (if_branch_returns || if_branch_stops) && (elif_branches_returns || elif_branches_stops) && (else_branch_returns || else_branch_stops); }, Stmt::While(while_stmt) => (_, _) = unreachable_code_branch_analysis_hazmat(&while_stmt.branch)?, + Stmt::For(for_stmt) => (_, _) = unreachable_code_branch_analysis_hazmat(&for_stmt.branch)?, // Non branching statements, so we safely ignore them + // I added them manually here to ensure I dont fuckk up in future if I add a new + // statement that contains branches Stmt::Const(_) | Stmt::Expr(_) | Stmt::VarDecl(_) | Stmt::VarDeclMulti(..) | Stmt::VarAssign(_) | Stmt::VarAssignMulti(..) | - Stmt::Lock(_) | Stmt::Unlock(_) | Stmt::For(_) => {} + Stmt::Lock(_) | Stmt::Unlock(_) => {} } } - Ok((certain_return_detected, stop_detected)) + Ok((certain_return_detected, certain_stop_detected)) } -/* -/// Performs return branch analysis code branch analysis, errors if it detects statements that could never be -/// reached due to `returns` and `breaks` statements. + +/// Performs return branch analysis on a statement (the last statement in a block), to ensure +/// that the statement (or its branch body) almost always certainly returns, or never returns. /// -/// NOTE: Do not call this function directly. this function only meant to be called within `code_analysis` function +/// This is a safe wrapper around `return_branch_analysis_hazmat` /// -fn return_branch_analysis_hazmat( - block: &Vec -) -> Result { - let mut certain_return_detected: bool = false; - let mut break_detected: bool = false; - - for stmt in block { - if certain_return_detected || break_detected { - let current_stmt_span = helpers::stmt_span(stmt); - let last_block_stmt_span = helpers::stmt_span(block.last().unwrap()); - - if current_stmt_span == last_block_stmt_span { - return Err(GoldError::Semantic(format!( - "Unreachable statement at line `{}`", - current_stmt_span.line - ))) - } else { - return Err(GoldError::Semantic(format!( - "Unreachable code starting from line `{}` up to line `{}`", - current_stmt_span.line, last_block_stmt_span.line - ))) +/// NOTE: Do not call this function directly. this function only meant to be called within `code_analysis` function, +/// and only after calling all other branch analysis functions such as empty branches and unreachable branches. +/// +fn return_branch_analysis_hazmat_wrapper( + func: &Function +) -> Result<(), GoldError> { + fn return_branch_analysis_hazmat( + last_stmt: &Stmt, + depth: usize + ) -> Result { + match last_stmt { + Stmt::Return(_) => Ok(true), + Stmt::Break(break_stmt) if depth == 1 => Err(GoldError::Semantic(format!("Cannot break out of infinite loop since it is the last statement in a returning function (line {} column {})", break_stmt.span.line, break_stmt.span.column))), + Stmt::Infinite(inf_stmt) => { + // We allow infinte loops to not return even in returning functions, as long as + // there's no breaks. + // + return_branch_analysis_hazmat(&inf_stmt.branch.last().unwrap(), depth + 1)?; + Ok(true) } - } - - match stmt { - Stmt::Return(_) => certain_return_detected = true, - Stmt::Break(_) => break_detected = true, - Stmt::Infinite(inf_stmt) => certain_return_detected = unreachable_code_branch_analysis_hazmat(&inf_stmt.branch)?, - Stmt::If(if_stmt) => if let Some(else_branch) = &if_stmt.else_branch { - let if_branch_returns = unreachable_code_branch_analysis_hazmat(&if_stmt.if_branch)?; - let else_branch_returns = unreachable_code_branch_analysis_hazmat(&else_branch)?; - - certain_return_detected = if_branch_returns && else_branch_returns; - }, - - // Everything else is ignored. - _ => {} - } - } - - Ok(certain_return_detected) -} -*/ - - - -// pub fn return_branch_analysis(block: &Vec, break_detected: bool, return_detected: bool) -> Result { - -/* -pub fn dead_code_analysis(block: &Vec, in_loop: bool) -> Result { - // Instead of returning error here, we panic, because if we returned an error here - // we would not have ability to pinpoint to the empty branch line. leaving responsiblity to - // caller is best. - // - assert!(!block.is_empty(), "(Compiler bug) we got called with an empty block. Always check block size before calling `dead_code_analysis`"); - - let mut end_detected = false; - - for stmt in block { - if end_detected { - let stmt_span = helpers::stmt_span(stmt); - - return Err(GoldError::Semantic(format!( - "Dead code detected starting from line `{}` up to the end of the scope", - stmt_span.line, - ))) - } - - match stmt { - Stmt::Return(_) => end_detected = true, - Stmt::Break(_) if in_loop => { - end_detected = true; - }, - Stmt::Infinite(infinite_stmt) => { - let body = &infinite_stmt.branch; - if body.is_empty() { - return Err(GoldError::Semantic(format!( - "Infinite loop branch has no statements. Empty branches are not allowed (line {} column {})", - infinite_stmt.span.line, infinite_stmt.span.column - ))) - } - - let inner_terminates = dead_code_analysis(body, true)?; - - if inner_terminates { - end_detected = true; - } - }, - - Stmt::While(while_stmt) => { - let body = &while_stmt.branch; - if body.is_empty() { - return Err(GoldError::Semantic(format!( - "While loop branch has no statements. Empty branches are not allowed (line {} column {})", - while_stmt.span.line, while_stmt.span.column - ))) - } - - dead_code_analysis(body, in_loop)?; - }, - - Stmt::For(for_stmt) => { - let body = &for_stmt.branch; - if body.is_empty() { - return Err(GoldError::Semantic(format!( - "For loop branch has no statements. Empty branches are not allowed (line {} column {})", - for_stmt.span.line, for_stmt.span.column - ))) - } - - dead_code_analysis(body, in_loop)?; - }, - - Stmt::If(if_stmt) => { - if if_stmt.if_branch.is_empty() { - return Err(GoldError::Semantic(format!( - "If statement main branch has no statements. Empty branches are not allowed (line {} column {})", - if_stmt.span.line, if_stmt.span.column - ))) - } + Stmt::If(if_stmt) if let Some(else_branch) = &if_stmt.else_branch => { + let if_branch_returns = return_branch_analysis_hazmat(&if_stmt.if_branch.last().unwrap(), depth)?; + let else_branch_returns = return_branch_analysis_hazmat(&else_branch.last().unwrap(), depth)?; - let if_term: bool = dead_code_analysis(&if_stmt.if_branch, in_loop)?; - - let mut elifs_term = true; + let mut elif_branches_returns = true; for s_vec in &if_stmt.elif_branches { - let expr_span = helpers::expr_span(&s_vec.0); - - if s_vec.1.is_empty() { - return Err(GoldError::Semantic(format!( - "If statement `elif` branch has no statements. Empty branches are not allowed (line {} column {})", - expr_span.line, expr_span.column - ))) - } - - if !dead_code_analysis(&s_vec.1, in_loop)? { - elifs_term = false; + if !return_branch_analysis_hazmat(&s_vec.1.last().unwrap(), depth)? { + elif_branches_returns = false; } } - // Check if statements branches all terminates - // - if let Some(else_branch) = &if_stmt.else_branch { - if else_branch.is_empty() { - return Err(GoldError::Semantic(format!( - "If statement `else` branch has no statements. Empty branches are not allowed (line {} column {})", - if_stmt.span.line, if_stmt.span.column - ))) - } - - let else_term = dead_code_analysis(else_branch, in_loop)?; - - if if_term && else_term && elifs_term { - end_detected = true; - } - } + Ok(if_branch_returns && elif_branches_returns && else_branch_returns) }, - _ => {} - } - - } - - Ok(end_detected) -} - - -#[expect(clippy::too_many_lines)] -pub fn return_branch_analysis( - func: &Function, - last_stmt: &Stmt, - is_loop: bool, - forbid_break: bool -) -> Result<(), GoldError> { - let ret_ty = func.return_type.as_ref().unwrap_or_else(|| panic!("(Compiler bug) Dont call return_branch_analysis on functions that dont have declared return type(s)!")); - - assert!(!func.body.is_empty(), "(Compiler bug) do not call return_branch_analysis on functions with empty bodies! Always check body size"); - match last_stmt { - Stmt::Break(break_stmt) => { - // Just a compiler bug guard to enforce the invariant. - assert!(is_loop, "(Compiler bug) check_stmts shouldve errored before we even got called. We got a break statement when we arent even in a loop!"); - - if forbid_break { - return Err(GoldError::Semantic(format!( - "You cannot `break` out of a infinite loop if its the last statement in a function that returns. Use a return statement instead. (line {} column {})", - break_stmt.span.line, break_stmt.span.column - ))) - } - - }, - Stmt::Return(_) => {}, - Stmt::Infinite(infinite_stmt) => { - // This is weak check, but I will keep it. It can catch (some) bugs. - assert!( - !infinite_stmt.branch.is_empty(), - "(Compiler bug) infinite loop branch is empty! this shouldve been caught by dead_code_analyse before calling us:\nFunc: {func:?}\ninfinite_stmt: {infinite_stmt:?}"); - - // If we are in a nested loop(s), we dont care about breaks or whatever. - // We only care about upper most level infinite loop. - // - // Otherwise, we execute this block which ensures you can't break out of the infinite - // loop because it's last statement in a function that returns + // These statements depend on specific expression values in order to execute, therefore they may not always execute (e.g. + // while loops, for loops, etc) + // so we cannot deduce for certain that they return // - if !is_loop { - // So, why do we error on break? can't programmer like break then return outside for - // loop? - // Answer is that return_branch_analysis is only called on last statemet, and if - // infinite loop is last statement, you can't break out of it. You can only return, or - // you dont return but you don't break. - // - for s in &infinite_stmt.branch { - match s { - Stmt::Break(break_stmt) => { - return Err(GoldError::Semantic(format!( - "You cannot `break` out of a infinite loop if its the last statement in a function that returns. Use a return statement instead. (line {} column {})", - break_stmt.span.line, break_stmt.span.column - ))) - }, - Stmt::If(_) => { - return_branch_analysis(func, s, true, true)?; - }, - Stmt::While(_) | Stmt::For(_) | Stmt::Infinite(_) => { - return_branch_analysis(func, s, true, false)?; - }, - - // Skip all other statements - _ => {} - } - } - } - } - - Stmt::While(while_stmt) => { - // If this is a nested loop, like a while loop inside a `infinite` loop, we let you do - // that. if in_loop is true, it might not be last statement after all. + // We only error thoug if they are at last statement on their own, and not, let's + // say, inside an infinite loop statement, in which cause they are fine to be. // - - assert!( - !while_stmt.branch.is_empty(), - "(Compiler bug) all branches must contain at least one statement, this shouldve been caught by dead_code_analyse before calling us:\nFunc: {func:?}\nwhile_stmt: {while_stmt:?}"); - - if !is_loop { - return Err(GoldError::Semantic(format!( - "While loops may or may not execute at all, therefore you need a return statement outside the loop scope, or consider using `infinite` loops instead. (line {} column {})", - while_stmt.span.line, while_stmt.span.column, - ))) - - } - }, + Stmt::While(while_stmt) if depth == 0 => Err(GoldError::Semantic(format!("While loop statements may or may not execute at all, therefore it cannot be the last statement in a returning function (line {} column {})", while_stmt.span.line, while_stmt.span.column))), - Stmt::For(for_stmt) => { - assert!(!for_stmt.branch.is_empty(), "(Compiler bug) all branches must contain at least one statement, this shouldve been caught by dead_code_analyse before calling us:\nFunc: {func:?}\nfor_stmt: {for_stmt:?}"); + Stmt::For(for_stmt) if depth == 0 => Err(GoldError::Semantic(format!("For loop statement may or may not execute at all, therefore it cannot be the last statement in a returning function (line {} column {})", for_stmt.span.line, for_stmt.span.column))), - if !is_loop { - return Err(GoldError::Semantic(format!( - "For loops may or may not execute at all, therefore you need a return statement outside the loop scope. (line {} column {})", - for_stmt.span.line, for_stmt.span.column, - ))) - } - }, - - Stmt::If(if_stmt) => { - // If we are not in a loop, then we only care about last statement of if branches - // bodies - if is_loop { - for stmt in &if_stmt.if_branch { - return_branch_analysis(func, stmt, is_loop, forbid_break)?; - } - - // We dont care if else branch is none, we in a loop. - if let Some(else_branch) = &if_stmt.else_branch { - for stmt in else_branch { - return_branch_analysis(func, stmt, is_loop, forbid_break)?; - } - } - - for s_vec in &if_stmt.elif_branches { - let body = &s_vec.1; - - for stmt in body { - return_branch_analysis(func, stmt, is_loop, forbid_break)?; - } - } - - } else { - let main_branch_last_stmt = if_stmt.if_branch.last().unwrap_or_else(|| { panic!( - "(Compiler bug) if statement main branch is empty! this shouldve been caught by dead_code_analyse before calling us:\nFunc: {func:?}\nif_stmt: {if_stmt:?}" - )}); - - return_branch_analysis(func, main_branch_last_stmt, is_loop, forbid_break)?; - - if let Some(else_branch) = &if_stmt.else_branch { - let else_branch_last_stmt = else_branch.last().unwrap_or_else(|| { panic!( - "(Compiler bug) if statement else branch is empty! this shouldve been caught by dead_code_analyse before calling us:\nFunc: {func:?}\nif_stmt: {if_stmt:?}" - )}); - return_branch_analysis(func, else_branch_last_stmt, is_loop, forbid_break)?; - } else { - return Err(GoldError::Semantic(format!( - "Function `{}` only returns in if statement branches, which might not always execute. Add an `else` branch (line {} column {})", - func.name, if_stmt.span.line, if_stmt.span.column, - ))); - } + // These statements don't have branches (or have failed the earlier guard checks), therefore, we can deduce for certain that they cannot return. + // + Stmt::While(_) | Stmt::For(_) | Stmt::Const(_) | Stmt::Expr(_) | Stmt::VarDecl(_) | Stmt::Continue(_) | Stmt::VarDeclMulti(..) | Stmt::VarAssign(_) | Stmt::VarAssignMulti(..) | + Stmt::Lock(_) | Stmt::Unlock(_) | Stmt::Break(_) | Stmt::If(_) => Ok(false) + } + } - for s_vec in &if_stmt.elif_branches { - let body = &s_vec.1; - let elif_branch_last_stmt = body.last().unwrap(); - return_branch_analysis(func, elif_branch_last_stmt, is_loop, forbid_break)?; - } - } - }, - other => { - if !is_loop { - let branch_span = helpers::stmt_span(other); + let last_stmt = func.body.last().unwrap(); - return Err(GoldError::Semantic(format!( - "Function `{}` declares return type(s) `{:?}`, but statement branch body does not end with a return statement (line {} column {})", - func.name, ret_ty, branch_span.line, branch_span.column, - ))) - } - }, + // Depth starts at 0 + let certainly_returns = return_branch_analysis_hazmat(last_stmt, 0)?; + if !certainly_returns { + return Err(GoldError::Semantic(format!("Expected function `{}` to return, but we found no return statements. (line {} column {})", func.name, func.span.line, func.span.column))) } Ok(()) } -*/ From 731d5e832f7517dceacd96d0afcd8e905a87b88b Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sat, 1 Aug 2026 19:20:14 +0300 Subject: [PATCH 10/15] tests: Clean-up semantics analysis layer unit tests structure --- src/lib.rs | 3 + src/semantic.rs | 2 - src/semantic/blackbox_tests.rs | 697 +--------------- src/semantic/blackbox_tests/return_tests.rs | 6 +- .../branch_analysis/branch_analysis_tests.rs | 11 + .../dead_code_analysis_tests.rs | 0 .../empty_branch_analysis_tests.rs | 26 + .../return_branch_analysis_tests.rs | 103 +-- src/semantic/branch_analysis_tests.rs | 136 ---- src/semantic_test_helpers.rs | 746 ++++++++++++++++++ 10 files changed, 846 insertions(+), 884 deletions(-) create mode 100644 src/semantic/branch_analysis/branch_analysis_tests.rs rename src/semantic/{ => branch_analysis}/branch_analysis_tests/dead_code_analysis_tests.rs (100%) create mode 100644 src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs rename src/semantic/{ => branch_analysis}/branch_analysis_tests/return_branch_analysis_tests.rs (87%) delete mode 100644 src/semantic/branch_analysis_tests.rs create mode 100644 src/semantic_test_helpers.rs diff --git a/src/lib.rs b/src/lib.rs index dccd375..cbda5cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,9 @@ pub mod consts; pub mod ast; #[cfg(test)] mod tests_consts; +#[cfg(test)] +mod semantic_test_helpers; + pub enum CompileInfo { CompileTo(String), diff --git a/src/semantic.rs b/src/semantic.rs index ca3608c..137eb5d 100644 --- a/src/semantic.rs +++ b/src/semantic.rs @@ -11,8 +11,6 @@ mod infer; mod helpers; -#[cfg(test)] -mod branch_analysis_tests; #[cfg(test)] mod helpers_tests; diff --git a/src/semantic/blackbox_tests.rs b/src/semantic/blackbox_tests.rs index 5baddd8..ede5dc0 100644 --- a/src/semantic/blackbox_tests.rs +++ b/src/semantic/blackbox_tests.rs @@ -1,11 +1,11 @@ use super::*; +use crate::semantic_test_helpers::*; use crate::ast::{ - FixedArraySize, IntLiteralValue, ArraySliceRange, + Expr, Type, FixedArraySize, IntLiteralValue, ArraySliceRange, UnaryOpKind, BinOpKind, - Param, VariableDeclaration, MultiVariableDeclaration, VariableAssignment, MultiAssignment, - IfStmt, WhileStmt, ForStmt, InfiniteStmt, BreakStmt, ContinueStmt, Constant + MultiVariableDeclaration, MultiAssignment, + IfStmt, WhileStmt, ForStmt, InfiniteStmt, BreakStmt, ContinueStmt }; - use crate::tests_consts::{ ALL_TYPES_NO_ARR, ALL_TYPES_NO_ARR_SCATTERED, ALL_TYPES_NO_ARR_NO_USIZE, ALL_TYPES_NO_INTS_NO_ARR, @@ -23,8 +23,6 @@ use crate::tests_consts::{ ALL_BIN_OP_KIND_COMP_ARTH }; -use std::sync::LazyLock; - mod const_tests; mod var_decl_tests; mod var_multi_decl_tests; @@ -67,690 +65,3 @@ mod continue_stmt_tests; mod happy_path_tests; -// Helper functions for all the test submodules -// - -// All types with dynamic array types -static ALL_TYPES_WITH_DYN_ARR: LazyLock> = LazyLock::new(|| { - vec![ - Type::Int8, - Type::Int16, - Type::Int32, - Type::Int64, - Type::Int128, - Type::Byte, - Type::Uint16, - Type::Uint32, - Type::Uint64, - Type::Uint128, - Type::Usize, - Type::Float64, - Type::Bool, - Type::Char, - Type::String, - - Type::Array(Box::new(Type::Int8)), - Type::Array(Box::new(Type::Int16)), - Type::Array(Box::new(Type::Int32)), - Type::Array(Box::new(Type::Int64)), - Type::Array(Box::new(Type::Int128)), - - Type::Array(Box::new(Type::Byte)), - Type::Array(Box::new(Type::Uint16)), - Type::Array(Box::new(Type::Uint32)), - Type::Array(Box::new(Type::Uint64)), - Type::Array(Box::new(Type::Uint128)), - Type::Array(Box::new(Type::Usize)), - - Type::Array(Box::new(Type::Float64)), - Type::Array(Box::new(Type::Bool)), - Type::Array(Box::new(Type::Char)), - Type::Array(Box::new(Type::String)), - ] -}); - -// All types, with only few integers (signed, and unsigned) with dynamic array types -// - - -static ALL_TYPES_FEW_INTS_WITH_DYN_ARR: LazyLock> = LazyLock::new(|| { - vec![ - Type::Uint128, - Type::Int128, - Type::Float64, - Type::Bool, - Type::Char, - Type::String, - - Type::Array(Box::new(Type::Uint128)), - Type::Array(Box::new(Type::Int128)), - Type::Array(Box::new(Type::Float64)), - Type::Array(Box::new(Type::Bool)), - Type::Array(Box::new(Type::Char)), - Type::Array(Box::new(Type::String)), - ] -}); - -static ALL_TYPES_FEW_INTS_WITH_DYN_ARR_SCATTERED: LazyLock> = LazyLock::new(|| { - vec![ - Type::Array(Box::new(Type::Bool)), - Type::Char, - Type::Bool, - Type::Uint128, - Type::Array(Box::new(Type::Float64)), - Type::Array(Box::new(Type::Char)), - Type::Float64, - Type::Array(Box::new(Type::Uint128)), - Type::Array(Box::new(Type::String)), - Type::Array(Box::new(Type::Int128)), - Type::Int128, - Type::String - ] -}); - - - -fn get_many_boolean_conditions() -> Vec { - let literals = get_all_literals(); - - let mut boolean_conds = vec![ - bool_lit(true), - bool_lit(false), - ]; - - for l in literals { - for b in ALL_BIN_OP_KIND_COMP { - // So that >= > <= < doesnt get performed on non integer/floats. - if !ALL_BIN_OP_KIND_COMP_EQ.contains(&b) { - match l { - Expr::StringLiteral { .. } | Expr::CharLiteral { .. } | Expr::BoolLiteral { .. } | Expr::ArrayLiteral { .. } => { - continue - }, - _ => {} - } - } - - let bin = Expr::BinOp { - left: Box::new(l.clone()), - right: Box::new(l.clone()), - op: b, - span: span(), - }; - - boolean_conds.push(bin); - } - } - - return boolean_conds; -} - -fn get_many_boolean_conditions_no_dyn_arr() -> Vec { - let literals = get_all_literals_no_arr(); - - let mut boolean_conds = vec![ - bool_lit(true), - bool_lit(false), - ]; - - for l in literals { - for b in ALL_BIN_OP_KIND_COMP { - // So that >= > <= < doesnt get performed on non integer/floats. - if !ALL_BIN_OP_KIND_COMP_EQ.contains(&b) { - match l { - Expr::StringLiteral { .. } | Expr::BoolLiteral { .. } | Expr::ArrayLiteral { .. } => { - continue - }, - _ => {} - } - } - - let bin = Expr::BinOp { - left: Box::new(l.clone()), - right: Box::new(l.clone()), - op: b, - span: span(), - }; - - boolean_conds.push(bin); - } - } - - return boolean_conds; -} - - -fn get_non_boolean_conditions() -> Vec { - let literals = get_all_literals(); - - let mut non_boolean_conds = vec![]; - - for l in literals { - if matches!(l, Expr::BoolLiteral { .. }) { - continue - } - non_boolean_conds.push(l.clone()); - - for b in ALL_BIN_OP_KIND_BIT_ARTH { - if !matches!(l, Expr::IntLiteral { .. }) { - continue - } - let bin = Expr::BinOp { - left: Box::new(l.clone()), - right: Box::new(l.clone()), - op: b, - span: span(), - }; - non_boolean_conds.push(bin); - } - } - - return non_boolean_conds; -} - - -fn get_all_literals_no_arr_bool() -> [Expr; 14] { - return [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - usize_lit(1), - - float64_lit(1.0), - - char_lit('f'), - str_lit("Hi") - ] -} - - -fn get_all_literals_no_arr_no_ints() -> [Expr; 4] { - let literals = [ - - float64_lit(1.0), - - bool_lit(false), - char_lit('f'), - str_lit("Hi") - ]; - - return literals; -} - -fn get_all_literals_few_ints() -> [Expr; 12] { - [ - uint128_lit(1), - int128_lit(1), - - float64_lit(1.0), - - bool_lit(false), - char_lit('f'), - str_lit("Hi"), - - array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), - array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), - array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), - - array_lit(vec![bool_lit(false), bool_lit(true) ], Some(Type::Array(Box::new(Type::Float64)))), - array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), - array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))) - ] -} - - -fn get_all_literals_few_ints_scattered() -> [Expr; 12] { - [ - array_lit(vec![bool_lit(false), bool_lit(true) ], Some(Type::Array(Box::new(Type::Bool)))), - char_lit('f'), - - bool_lit(false), - uint128_lit(1), - array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), - array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), - float64_lit(1.0), - - array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), - array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))), - array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), - int128_lit(1), - str_lit("Hi") - ] -} - -fn get_all_literals_no_arr_few_ints() -> [Expr; 6] { - let literals = [ - uint128_lit(1), - int128_lit(1), - - float64_lit(1.0), - - bool_lit(false), - char_lit('f'), - str_lit("Hi") - ]; - - return literals; -} - - -fn get_all_literals_no_arr_few_ints_scattered() -> [Expr; 6] { - let literals = [ - str_lit("Hi"), - - bool_lit(false), - int128_lit(1), - char_lit('f'), - float64_lit(1.0), - uint128_lit(1), - ]; - - return literals; -} - - -fn get_all_signed_literals_no_arr() -> [Expr; 6] { - let literals = [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - float64_lit(1.0), - ]; - - return literals; -} - - -fn get_all_signed_literals_no_arr_no_float() -> [Expr; 5] { - let literals = [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - ]; - - return literals; -} - - - -fn get_all_unsigned_literals_no_arr() -> [Expr; 6] { - let literals = [ - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - usize_lit(1) - ]; - - return literals; -} - - -fn get_all_literals_no_arr_str_bool() -> [Expr; 12] { - let literals = [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - usize_lit(1), - - float64_lit(1.0), - ]; - - return literals; -} - - - -fn get_all_literals_no_arr_str_bool_scattered() -> [Expr; 12] { - let literals = [ - uint32_lit(1), - int8_lit(1), - int64_lit(1), - uint128_lit(1), - - uint16_lit(1), - usize_lit(1), - int16_lit(1), - byte_lit(1), - float64_lit(1.0), - uint64_lit(1), - int128_lit(1), - int32_lit(1), - - ]; - - return literals; -} - - - - -fn get_all_literals_no_arr_str_bool_float() -> [Expr; 11] { - let literals = [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - usize_lit(1), - ]; - - return literals; -} - -fn get_all_literals_no_arr() -> [Expr; 15] { - [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - usize_lit(1), - - float64_lit(1.0), - - bool_lit(false), - char_lit('f'), - str_lit("Hi") - ] -} - -fn get_all_literals_no_arr_scattered_order() -> [Expr; 15] { - [ - int128_lit(1), - int8_lit(1), - uint64_lit(1), - uint16_lit(1), - int64_lit(1), - str_lit("Hi"), - uint128_lit(1), - float64_lit(1.0), - uint32_lit(1), - char_lit('f'), - int16_lit(1), - bool_lit(false), - byte_lit(1), - int32_lit(1), - usize_lit(1) - ] -} - - - -fn get_all_literals_no_arr_no_usize() -> [Expr; 14] { - return [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - float64_lit(1.0), - - bool_lit(false), - char_lit('f'), - str_lit("Hi") - ] -} - -fn get_all_literals() -> [Expr; 30] { - return [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - usize_lit(1), - - float64_lit(1.0), - - bool_lit(false), - char_lit('f'), - str_lit("Hi"), - - array_lit(vec![int8_lit(1), int8_lit(i8::MIN), int8_lit(i8::MAX) ], Some(Type::Array(Box::new(Type::Int8)))), - array_lit(vec![int16_lit(1), int16_lit(i16::MIN), int16_lit(i16::MAX) ], Some(Type::Array(Box::new(Type::Int16)))), - array_lit(vec![int32_lit(1), int32_lit(i32::MIN), int32_lit(i32::MAX) ], Some(Type::Array(Box::new(Type::Int32)))), - array_lit(vec![int64_lit(1), int64_lit(i64::MIN), int64_lit(i64::MAX) ], Some(Type::Array(Box::new(Type::Int64)))), - array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), - - array_lit(vec![byte_lit(1), byte_lit(u8::MIN), byte_lit(u8::MAX) ], Some(Type::Array(Box::new(Type::Byte)))), - array_lit(vec![uint16_lit(1), uint16_lit(u16::MIN), uint16_lit(u16::MAX) ], Some(Type::Array(Box::new(Type::Uint16)))), - array_lit(vec![uint32_lit(1), uint32_lit(u32::MIN), uint32_lit(u32::MAX) ], Some(Type::Array(Box::new(Type::Uint32)))), - array_lit(vec![uint64_lit(1), uint64_lit(u64::MIN), uint64_lit(u64::MAX) ], Some(Type::Array(Box::new(Type::Uint64)))), - array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), - array_lit(vec![usize_lit(1), usize_lit(usize::MIN), usize_lit(usize::MAX) ], Some(Type::Array(Box::new(Type::Usize)))), - - array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), - array_lit(vec![bool_lit(false), bool_lit(true)], Some(Type::Array(Box::new(Type::Bool)))), - - array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), - array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))) - ]; -} - - - - - -fn span() -> Span { - Span { line: 1, column: 0 } -} - -/// Build an AST that contains exactly one function. -fn ast_one(func: Function) -> AST { - AST { functions: vec![func], globals: vec![] } -} - -/// Build a void function (no return type) with the given body. -fn void_func(name: &str, params: Vec, mut body: Vec) -> Function { - if body.len() == 0 { - // Dummy body because empty branches are not allowed. - body = vec![var_decl(true, "x", Type::Int8, int32_lit(69))]; - } - - Function { - name: name.to_string(), - params, - return_type: None, - body, - span: span(), - } -} - -/// Build a function that returns a single type. -fn returning_func(name: &str, params: Vec, ret: Vec, body: Vec) -> Function { - Function { - name: name.to_string(), - params, - return_type: Some(ret), - body, - span: span(), - } -} - -fn param(name: &str, ty: Type) -> Param { - Param { name: name.to_string(), type_name: ty, span: span() } -} - - -fn const_define_locally(name: &str, ty: Type, value: Expr) -> Stmt { - Stmt::Const(Constant { - name: name.to_string(), - type_name: ty, - value, - span: span(), - }) -} - -fn const_define_globally(name: &str, ty: Type, value: Expr) -> GlobalStmt { - GlobalStmt::Const(Constant { - name: name.to_string(), - type_name: ty, - value, - span: span(), - }) -} - -fn var_decl(explicitly_initialized: bool, name: &str, ty: Type, value: Expr) -> Stmt { - Stmt::VarDecl(VariableDeclaration { - name: name.to_string(), - type_name: ty, - value, - explicitly_initialized, - span: span(), - }) -} - - -fn var_assign(name: &str, value: Expr) -> Stmt { - Stmt::VarAssign(VariableAssignment { - name: name.to_string(), - value, - span: span(), - }) -} - -fn contains_array_literal(expr: &Expr) -> bool { - match expr { - Expr::ArrayLiteral { .. } => true, - Expr::BinOp { left, right, .. } => { - contains_array_literal(left) || contains_array_literal(right) - } - _ => false, - } -} - - -fn array_lit(exprs: Vec, type_name: Option) -> Expr { - Expr::ArrayLiteral { elements: exprs, type_name, span: span() } -} - -fn int8_lit(n: i8) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int8(n), span: span() } -} - -fn int16_lit(n: i16) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int16(n), span: span() } -} - -fn int32_lit(n: i32) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int32(n), span: span() } -} - -fn int64_lit(n: i64) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int64(n), span: span() } -} - -fn int128_lit(n: i128) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int128(n), span: span() } -} - - - -fn byte_lit(b: u8) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Byte(b), span: span() } -} - -fn uint16_lit(n: u16) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint16(n), span: span() } -} - -fn uint32_lit(n: u32) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint32(n), span: span() } -} - -fn uint64_lit(n: u64) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint64(n), span: span() } -} - -fn uint128_lit(n: u128) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint128(n), span: span() } -} - - -fn usize_lit(n: usize) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Usize(n), span: span() } -} - - - -fn float64_lit(f: f64) -> Expr { - Expr::Float64Literal { value: f, span: span() } -} - - -fn bool_lit(b: bool) -> Expr { - Expr::BoolLiteral { value: b, span: span() } -} - -fn char_lit(c: char) -> Expr { - Expr::CharLiteral { value: c, span: span() } -} - -fn str_lit(s: &str) -> Expr { - Expr::StringLiteral { value: s.to_string(), span: span() } -} - -fn var_expr(name: &str) -> Expr { - Expr::Var { name: name.to_string(), span: span() } -} - -fn call_expr(name: &str, args: Vec) -> Expr { - Expr::Call { name: name.to_string(), args, span: span() } -} - -fn return_stmt(exprs: Vec) -> Stmt { - Stmt::Return(exprs) -} - diff --git a/src/semantic/blackbox_tests/return_tests.rs b/src/semantic/blackbox_tests/return_tests.rs index 38c22cb..0737efe 100644 --- a/src/semantic/blackbox_tests/return_tests.rs +++ b/src/semantic/blackbox_tests/return_tests.rs @@ -19,7 +19,7 @@ mod return_tests { let mut ast = ast_one(func); let result = check_semantics(&mut ast); assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("Dead code detected")); + assert!(result.unwrap_err().to_string().contains("Unreachable statement")); } } @@ -35,9 +35,7 @@ mod return_tests { let mut ast = ast_one(func); let result = check_semantics(&mut ast); assert!(result.is_err()); - let err = result.unwrap_err().to_string(); - assert!(err.starts_with("Semantic error: Function `foo` declares return type(s)")); - assert!(err.contains("but statement branch body does not end with a return statement")); + assert!(result.unwrap_err().to_string().contains("Expected function `foo` to return, but we found no return statements")) } } diff --git a/src/semantic/branch_analysis/branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests.rs new file mode 100644 index 0000000..b6efd1e --- /dev/null +++ b/src/semantic/branch_analysis/branch_analysis_tests.rs @@ -0,0 +1,11 @@ +use super::*; + +use crate::semantic_test_helpers::*; + +use crate::ast::{ + InfiniteStmt +}; + +mod empty_branch_analysis_tests; +mod return_branch_analysis_tests; + diff --git a/src/semantic/branch_analysis_tests/dead_code_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/dead_code_analysis_tests.rs similarity index 100% rename from src/semantic/branch_analysis_tests/dead_code_analysis_tests.rs rename to src/semantic/branch_analysis/branch_analysis_tests/dead_code_analysis_tests.rs diff --git a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs new file mode 100644 index 0000000..8522316 --- /dev/null +++ b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs @@ -0,0 +1,26 @@ +use super::*; + +#[cfg(test)] +mod empty_branch_analysis_tests { + use super::*; + + #[test] + #[should_panic(expected="Compiler bug")] + // The reason `empty_branch_analysis_hazmat` panics when fed empty block of code directly, is + // because it expects caller to give it an initial, non empty block of code. because if it were + // given empty block of code, the function wouldn't be able to print error with line and column, and i dont want keep + // passing spans all over. + // + fn empty_block_of_code_panics() { + let _ = empty_branch_analysis_hazmat(&vec![]); + } + + #[test] + fn empty_infinite_statement() { + for i in 1..1000 { + let result = empty_branch_analysis_hazmat(&vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span() }); i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } +} diff --git a/src/semantic/branch_analysis_tests/return_branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/return_branch_analysis_tests.rs similarity index 87% rename from src/semantic/branch_analysis_tests/return_branch_analysis_tests.rs rename to src/semantic/branch_analysis/branch_analysis_tests/return_branch_analysis_tests.rs index 098e681..0b79a67 100644 --- a/src/semantic/branch_analysis_tests/return_branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests/return_branch_analysis_tests.rs @@ -1,33 +1,37 @@ use super::*; #[cfg(test)] -mod return_branch_analysis_tests { +mod return_branch_analysis_hazmat_wrapper_tests { use super::*; - #[should_panic(expected = "Compiler bug")] #[test] - fn func_has_no_declared_return_type_panics() { + #[should_panic] + fn func_is_empty_panics() { let dummy_func = Function { - name: "x".to_string(), params: vec![], return_type: None, body: vec![Stmt::Expr(int64_lit(69))], span: span() + name: "foo".to_string(), params: vec![], return_type: None, body: vec![], span: span() }; - - let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func); } - #[test] fn func_never_returns() { - let dummy_func = make_dummy_func("x".to_string(), None); - let last_stmt = dummy_func.body.last().unwrap(); + let literals = get_all_literals_with_var_and_var_arr(); + + for l in literals { + for t in ALL_TYPES_WITH_DYN_ARR.iter() { + let dummy_func = returning_func(&"foo", vec![], vec![t.clone()], vec![Stmt::Expr(l.clone())]); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("statement branch body does not end with a return statement")); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Expected function `foo` to return, but we found no return statements")); + } + } } +} +/* #[test] fn func_returns() { let literals_with_var = get_all_literals_with_var_no_arr(); @@ -37,15 +41,14 @@ mod return_branch_analysis_tests { ])); let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } } - - // Must trigger a guard panic that is meant to catch misuse of return_branch_analysis + // Must trigger a guard panic that is meant to catch misuse of return_branch_analysis_hazmat_wrapper #[should_panic(expected = "Compiler bug")] #[test] fn func_break_without_loop_panics() { @@ -54,11 +57,11 @@ mod return_branch_analysis_tests { ])); let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); } - // Empty branches should panic because return_branch_analysis assumes + // Empty branches should panic because return_branch_analysis_hazmat_wrapper assumes // all function branches contain at least 1 statement, which // is what is guaranteed by dead_code_analysis. #[should_panic(expected = "Compiler bug")] @@ -73,7 +76,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); } // Same as above test, but this time nested. @@ -100,7 +103,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); } @@ -120,7 +123,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("You cannot `break` out of a infinite loop if its the last statement in a function that returns")); @@ -149,7 +152,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("You cannot `break` out of a infinite loop if its the last statement in a function that returns")); @@ -178,7 +181,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("You cannot `break` out of a infinite loop if its the last statement in a function that returns")); @@ -209,7 +212,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("You cannot `break` out of a infinite loop if its the last statement in a function that returns")); @@ -238,7 +241,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } } @@ -265,7 +268,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } } @@ -294,7 +297,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } } @@ -323,7 +326,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("You cannot `break` out of a infinite loop if its the last statement in a function that returns")); @@ -350,7 +353,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -389,7 +392,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -427,7 +430,7 @@ mod return_branch_analysis_tests { let dummy_func = make_dummy_func("x".to_string(), Some(stmts)); let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -454,7 +457,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().starts_with( @@ -484,7 +487,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().starts_with( @@ -515,7 +518,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().starts_with( @@ -540,13 +543,13 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); } // Same as above, but this time main branch contains return, but else branch is Some, but empty. (this should panic because - // return_branch_analysis assumes all function branches contain at least 1 statement, which + // return_branch_analysis_hazmat_wrapper assumes all function branches contain at least 1 statement, which // is what is guaranteed by dead_code_analysis.) #[should_panic(expected = "Compiler bug")] #[test] @@ -565,11 +568,11 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); } // Same as above, but this time main branch empty, and else branch returns. (this should panic because - // return_branch_analysis assumes all function branches contain at least 1 statement, which + // return_branch_analysis_hazmat_wrapper assumes all function branches contain at least 1 statement, which // is what is guaranteed by dead_code_analysis.) #[should_panic] #[test] @@ -588,7 +591,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let _ = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let _ = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); } @@ -614,7 +617,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("but statement branch body does not end with a return statement")); @@ -644,7 +647,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("but statement branch body does not end with a return statement")); @@ -675,7 +678,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -706,7 +709,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("but statement branch body does not end with a return statement")); @@ -739,7 +742,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("but statement branch body does not end with a return statement")); @@ -773,7 +776,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("but statement branch body does not end with a return statement")); @@ -810,7 +813,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -840,7 +843,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -872,7 +875,7 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } @@ -905,9 +908,11 @@ mod return_branch_analysis_tests { let last_stmt = dummy_func.body.last().unwrap(); - let result = return_branch_analysis(&dummy_func, &last_stmt, false, false); + let result = return_branch_analysis_hazmat_wrapper(&dummy_func, &last_stmt, false, false); assert!(result.is_ok()); } } } + +*/ diff --git a/src/semantic/branch_analysis_tests.rs b/src/semantic/branch_analysis_tests.rs deleted file mode 100644 index b43bce5..0000000 --- a/src/semantic/branch_analysis_tests.rs +++ /dev/null @@ -1,136 +0,0 @@ -use super::*; - -use crate::ast::{ - IntLiteralValue, - ForStmt, IfStmt, WhileStmt, InfiniteStmt, BreakStmt -}; - -use crate::semantic::branch_analysis::{ - dead_code_analysis, - return_branch_analysis -}; - - -mod dead_code_analysis_tests; -mod return_branch_analysis_tests; - -// Test Helpers - -fn span() -> Span { - Span { line: 1, column: 1 } -} - -fn int8_lit(n: i8) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int8(n), span: span() } -} - -fn int16_lit(n: i16) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int16(n), span: span() } -} - -fn int32_lit(n: i32) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int32(n), span: span() } -} - -fn int64_lit(n: i64) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int64(n), span: span() } -} - -fn int128_lit(n: i128) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Int128(n), span: span() } -} - - - -fn byte_lit(b: u8) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Byte(b), span: span() } -} - -fn uint16_lit(n: u16) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint16(n), span: span() } -} - -fn uint32_lit(n: u32) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint32(n), span: span() } -} - -fn uint64_lit(n: u64) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint64(n), span: span() } -} - -fn uint128_lit(n: u128) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Uint128(n), span: span() } -} - - -fn usize_lit(n: usize) -> Expr { - Expr::IntLiteral { value: IntLiteralValue::Usize(n), span: span() } -} - - - -fn float64_lit(f: f64) -> Expr { - Expr::Float64Literal { value: f, span: span() } -} - - -fn bool_lit(b: bool) -> Expr { - Expr::BoolLiteral { value: b, span: span() } -} - -fn str_lit(s: &str) -> Expr { - Expr::StringLiteral { value: s.to_string(), span: span() } -} - -fn make_dummy_func(name: String, body: Option>) -> Function { - if body.is_none() { - return Function { - name: name, params: vec![], return_type: Some(vec![Type::Int32]), body: vec![Stmt::Expr(int64_lit(69))], span: span() - }; - } else { - return Function { - name: name, params: vec![], return_type: Some(vec![Type::Int32]), body: body.unwrap(), span: span() - }; - } -} - - -fn make_return_stmt(exprs: Vec) -> Stmt { - Stmt::Return(exprs) -} - -fn make_break_stmt() -> Stmt { - Stmt::Break(BreakStmt { span: span() }) -} - -fn var_expr(name: &str) -> Expr { - Expr::Var { name: name.to_string(), span: span() } -} - - -fn get_all_literals_with_var_no_arr() -> [Expr; 15] { - let literals = [ - int8_lit(1), - int16_lit(1), - int32_lit(1), - int64_lit(1), - int128_lit(1), - - byte_lit(1), - uint16_lit(1), - uint32_lit(1), - uint64_lit(1), - uint128_lit(1), - - usize_lit(1), - - float64_lit(1.0), - - bool_lit(false), - str_lit("Hi"), - var_expr("a") - ]; - - return literals; -} - diff --git a/src/semantic_test_helpers.rs b/src/semantic_test_helpers.rs new file mode 100644 index 0000000..3547414 --- /dev/null +++ b/src/semantic_test_helpers.rs @@ -0,0 +1,746 @@ +use crate::ast::{ + AST, Expr, Stmt, GlobalStmt, Type, Constant, IntLiteralValue, Span, + Function, Param, VariableDeclaration, VariableAssignment +}; + +use crate::tests_consts::{ + ALL_BIN_OP_KIND_COMP, ALL_BIN_OP_KIND_COMP_EQ, ALL_BIN_OP_KIND_BIT_ARTH +}; + +use std::sync::LazyLock; + +// Helper functions for all the semantics analysis layer test submodules +// + +// All types with dynamic array types +pub static ALL_TYPES_WITH_DYN_ARR: LazyLock> = LazyLock::new(|| { + vec![ + Type::Int8, + Type::Int16, + Type::Int32, + Type::Int64, + Type::Int128, + Type::Byte, + Type::Uint16, + Type::Uint32, + Type::Uint64, + Type::Uint128, + Type::Usize, + Type::Float64, + Type::Bool, + Type::Char, + Type::String, + + Type::Array(Box::new(Type::Int8)), + Type::Array(Box::new(Type::Int16)), + Type::Array(Box::new(Type::Int32)), + Type::Array(Box::new(Type::Int64)), + Type::Array(Box::new(Type::Int128)), + + Type::Array(Box::new(Type::Byte)), + Type::Array(Box::new(Type::Uint16)), + Type::Array(Box::new(Type::Uint32)), + Type::Array(Box::new(Type::Uint64)), + Type::Array(Box::new(Type::Uint128)), + Type::Array(Box::new(Type::Usize)), + + Type::Array(Box::new(Type::Float64)), + Type::Array(Box::new(Type::Bool)), + Type::Array(Box::new(Type::Char)), + Type::Array(Box::new(Type::String)), + ] +}); + +// All types, with only few integers (signed, and unsigned) with dynamic array types +// + + +pub static ALL_TYPES_FEW_INTS_WITH_DYN_ARR: LazyLock> = LazyLock::new(|| { + vec![ + Type::Uint128, + Type::Int128, + Type::Float64, + Type::Bool, + Type::Char, + Type::String, + + Type::Array(Box::new(Type::Uint128)), + Type::Array(Box::new(Type::Int128)), + Type::Array(Box::new(Type::Float64)), + Type::Array(Box::new(Type::Bool)), + Type::Array(Box::new(Type::Char)), + Type::Array(Box::new(Type::String)), + ] +}); + +pub static ALL_TYPES_FEW_INTS_WITH_DYN_ARR_SCATTERED: LazyLock> = LazyLock::new(|| { + vec![ + Type::Array(Box::new(Type::Bool)), + Type::Char, + Type::Bool, + Type::Uint128, + Type::Array(Box::new(Type::Float64)), + Type::Array(Box::new(Type::Char)), + Type::Float64, + Type::Array(Box::new(Type::Uint128)), + Type::Array(Box::new(Type::String)), + Type::Array(Box::new(Type::Int128)), + Type::Int128, + Type::String + ] +}); + + + +pub fn get_many_boolean_conditions() -> Vec { + let literals = get_all_literals(); + + let mut boolean_conds = vec![ + bool_lit(true), + bool_lit(false), + ]; + + for l in literals { + for b in ALL_BIN_OP_KIND_COMP { + // So that >= > <= < doesnt get performed on non integer/floats. + if !ALL_BIN_OP_KIND_COMP_EQ.contains(&b) { + match l { + Expr::StringLiteral { .. } | Expr::CharLiteral { .. } | Expr::BoolLiteral { .. } | Expr::ArrayLiteral { .. } => { + continue + }, + _ => {} + } + } + + let bin = Expr::BinOp { + left: Box::new(l.clone()), + right: Box::new(l.clone()), + op: b, + span: span(), + }; + + boolean_conds.push(bin); + } + } + + return boolean_conds; +} + +pub fn get_many_boolean_conditions_no_dyn_arr() -> Vec { + let literals = get_all_literals_no_arr(); + + let mut boolean_conds = vec![ + bool_lit(true), + bool_lit(false), + ]; + + for l in literals { + for b in ALL_BIN_OP_KIND_COMP { + // So that >= > <= < doesnt get performed on non integer/floats. + if !ALL_BIN_OP_KIND_COMP_EQ.contains(&b) { + match l { + Expr::StringLiteral { .. } | Expr::BoolLiteral { .. } | Expr::ArrayLiteral { .. } => { + continue + }, + _ => {} + } + } + + let bin = Expr::BinOp { + left: Box::new(l.clone()), + right: Box::new(l.clone()), + op: b, + span: span(), + }; + + boolean_conds.push(bin); + } + } + + return boolean_conds; +} + + +pub fn get_non_boolean_conditions() -> Vec { + let literals = get_all_literals(); + + let mut non_boolean_conds = vec![]; + + for l in literals { + if matches!(l, Expr::BoolLiteral { .. }) { + continue + } + non_boolean_conds.push(l.clone()); + + for b in ALL_BIN_OP_KIND_BIT_ARTH { + if !matches!(l, Expr::IntLiteral { .. }) { + continue + } + let bin = Expr::BinOp { + left: Box::new(l.clone()), + right: Box::new(l.clone()), + op: b, + span: span(), + }; + non_boolean_conds.push(bin); + } + } + + return non_boolean_conds; +} + + +pub fn get_all_literals_no_arr_bool() -> [Expr; 14] { + return [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + usize_lit(1), + + float64_lit(1.0), + + char_lit('f'), + str_lit("Hi") + ] +} + + +pub fn get_all_literals_no_arr_no_ints() -> [Expr; 4] { + let literals = [ + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi") + ]; + + return literals; +} + +pub fn get_all_literals_few_ints() -> [Expr; 12] { + [ + uint128_lit(1), + int128_lit(1), + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi"), + + array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), + array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), + array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), + + array_lit(vec![bool_lit(false), bool_lit(true) ], Some(Type::Array(Box::new(Type::Float64)))), + array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), + array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))) + ] +} + + +pub fn get_all_literals_few_ints_scattered() -> [Expr; 12] { + [ + array_lit(vec![bool_lit(false), bool_lit(true) ], Some(Type::Array(Box::new(Type::Bool)))), + char_lit('f'), + + bool_lit(false), + uint128_lit(1), + array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), + array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), + float64_lit(1.0), + + array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), + array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))), + array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), + int128_lit(1), + str_lit("Hi") + ] +} + +pub fn get_all_literals_no_arr_few_ints() -> [Expr; 6] { + let literals = [ + uint128_lit(1), + int128_lit(1), + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi") + ]; + + return literals; +} + + +pub fn get_all_literals_no_arr_few_ints_scattered() -> [Expr; 6] { + let literals = [ + str_lit("Hi"), + + bool_lit(false), + int128_lit(1), + char_lit('f'), + float64_lit(1.0), + uint128_lit(1), + ]; + + return literals; +} + + +pub fn get_all_signed_literals_no_arr() -> [Expr; 6] { + let literals = [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + float64_lit(1.0), + ]; + + return literals; +} + + +pub fn get_all_signed_literals_no_arr_no_float() -> [Expr; 5] { + let literals = [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + ]; + + return literals; +} + + + +pub fn get_all_unsigned_literals_no_arr() -> [Expr; 6] { + let literals = [ + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + usize_lit(1) + ]; + + return literals; +} + + +pub fn get_all_literals_no_arr_str_bool() -> [Expr; 12] { + let literals = [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + usize_lit(1), + + float64_lit(1.0), + ]; + + return literals; +} + + + +pub fn get_all_literals_no_arr_str_bool_scattered() -> [Expr; 12] { + let literals = [ + uint32_lit(1), + int8_lit(1), + int64_lit(1), + uint128_lit(1), + + uint16_lit(1), + usize_lit(1), + int16_lit(1), + byte_lit(1), + float64_lit(1.0), + uint64_lit(1), + int128_lit(1), + int32_lit(1), + + ]; + + return literals; +} + + + + +pub fn get_all_literals_no_arr_str_bool_float() -> [Expr; 11] { + let literals = [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + usize_lit(1), + ]; + + return literals; +} + +pub fn get_all_literals_no_arr() -> [Expr; 15] { + [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + usize_lit(1), + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi") + ] +} + +pub fn get_all_literals_no_arr_scattered_order() -> [Expr; 15] { + [ + int128_lit(1), + int8_lit(1), + uint64_lit(1), + uint16_lit(1), + int64_lit(1), + str_lit("Hi"), + uint128_lit(1), + float64_lit(1.0), + uint32_lit(1), + char_lit('f'), + int16_lit(1), + bool_lit(false), + byte_lit(1), + int32_lit(1), + usize_lit(1) + ] +} + + + +pub fn get_all_literals_no_arr_no_usize() -> [Expr; 14] { + return [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi") + ] +} + +pub fn get_all_literals() -> [Expr; 30] { + return [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + usize_lit(1), + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi"), + + array_lit(vec![int8_lit(1), int8_lit(i8::MIN), int8_lit(i8::MAX) ], Some(Type::Array(Box::new(Type::Int8)))), + array_lit(vec![int16_lit(1), int16_lit(i16::MIN), int16_lit(i16::MAX) ], Some(Type::Array(Box::new(Type::Int16)))), + array_lit(vec![int32_lit(1), int32_lit(i32::MIN), int32_lit(i32::MAX) ], Some(Type::Array(Box::new(Type::Int32)))), + array_lit(vec![int64_lit(1), int64_lit(i64::MIN), int64_lit(i64::MAX) ], Some(Type::Array(Box::new(Type::Int64)))), + array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), + + array_lit(vec![byte_lit(1), byte_lit(u8::MIN), byte_lit(u8::MAX) ], Some(Type::Array(Box::new(Type::Byte)))), + array_lit(vec![uint16_lit(1), uint16_lit(u16::MIN), uint16_lit(u16::MAX) ], Some(Type::Array(Box::new(Type::Uint16)))), + array_lit(vec![uint32_lit(1), uint32_lit(u32::MIN), uint32_lit(u32::MAX) ], Some(Type::Array(Box::new(Type::Uint32)))), + array_lit(vec![uint64_lit(1), uint64_lit(u64::MIN), uint64_lit(u64::MAX) ], Some(Type::Array(Box::new(Type::Uint64)))), + array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), + array_lit(vec![usize_lit(1), usize_lit(usize::MIN), usize_lit(usize::MAX) ], Some(Type::Array(Box::new(Type::Usize)))), + + array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), + array_lit(vec![bool_lit(false), bool_lit(true)], Some(Type::Array(Box::new(Type::Bool)))), + + array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), + array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))) + ]; +} + +pub fn get_all_literals_with_var_and_var_arr() -> [Expr; 32] { + return [ + int8_lit(1), + int16_lit(1), + int32_lit(1), + int64_lit(1), + int128_lit(1), + + byte_lit(1), + uint16_lit(1), + uint32_lit(1), + uint64_lit(1), + uint128_lit(1), + + usize_lit(1), + + float64_lit(1.0), + + bool_lit(false), + char_lit('f'), + str_lit("Hi"), + + var_expr("x"), + + array_lit(vec![int8_lit(1), int8_lit(i8::MIN), int8_lit(i8::MAX) ], Some(Type::Array(Box::new(Type::Int8)))), + array_lit(vec![int16_lit(1), int16_lit(i16::MIN), int16_lit(i16::MAX) ], Some(Type::Array(Box::new(Type::Int16)))), + array_lit(vec![int32_lit(1), int32_lit(i32::MIN), int32_lit(i32::MAX) ], Some(Type::Array(Box::new(Type::Int32)))), + array_lit(vec![int64_lit(1), int64_lit(i64::MIN), int64_lit(i64::MAX) ], Some(Type::Array(Box::new(Type::Int64)))), + array_lit(vec![int128_lit(1), int128_lit(i128::MIN), int128_lit(i128::MAX) ], Some(Type::Array(Box::new(Type::Int128)))), + + array_lit(vec![byte_lit(1), byte_lit(u8::MIN), byte_lit(u8::MAX) ], Some(Type::Array(Box::new(Type::Byte)))), + array_lit(vec![uint16_lit(1), uint16_lit(u16::MIN), uint16_lit(u16::MAX) ], Some(Type::Array(Box::new(Type::Uint16)))), + array_lit(vec![uint32_lit(1), uint32_lit(u32::MIN), uint32_lit(u32::MAX) ], Some(Type::Array(Box::new(Type::Uint32)))), + array_lit(vec![uint64_lit(1), uint64_lit(u64::MIN), uint64_lit(u64::MAX) ], Some(Type::Array(Box::new(Type::Uint64)))), + array_lit(vec![uint128_lit(1), uint128_lit(u128::MIN), uint128_lit(u128::MAX) ], Some(Type::Array(Box::new(Type::Uint128)))), + array_lit(vec![usize_lit(1), usize_lit(usize::MIN), usize_lit(usize::MAX) ], Some(Type::Array(Box::new(Type::Usize)))), + + array_lit(vec![float64_lit(1.0), float64_lit(f64::MIN), float64_lit(f64::MAX) ], Some(Type::Array(Box::new(Type::Float64)))), + array_lit(vec![bool_lit(false), bool_lit(true)], Some(Type::Array(Box::new(Type::Bool)))), + + array_lit(vec![char_lit('\n'), char_lit('H'), char_lit('!')], Some(Type::Array(Box::new(Type::Char)))), + array_lit(vec![str_lit(""), str_lit("Hi"), str_lit(" !")], Some(Type::Array(Box::new(Type::String)))), + + array_lit(vec![var_expr("x"), var_expr("y"), var_expr("z")], Some(Type::Array(Box::new(Type::String)))), + + ]; +} + + + + + +pub fn span() -> Span { + Span { line: 1, column: 0 } +} + +/// Build an AST that contains exactly one function. +pub fn ast_one(func: Function) -> AST { + AST { functions: vec![func], globals: vec![] } +} + +/// Build a void function (no return type) with the given body. +pub fn void_func(name: &str, params: Vec, mut body: Vec) -> Function { + if body.len() == 0 { + // Dummy body because empty branches are not allowed. + body = vec![var_decl(true, "x", Type::Int8, int32_lit(69))]; + } + + Function { + name: name.to_string(), + params, + return_type: None, + body, + span: span(), + } +} + +/// Build a function that returns a single type. +pub fn returning_func(name: &str, params: Vec, ret: Vec, body: Vec) -> Function { + Function { + name: name.to_string(), + params, + return_type: Some(ret), + body, + span: span(), + } +} + +pub fn param(name: &str, ty: Type) -> Param { + Param { name: name.to_string(), type_name: ty, span: span() } +} + + +pub fn const_define_locally(name: &str, ty: Type, value: Expr) -> Stmt { + Stmt::Const(Constant { + name: name.to_string(), + type_name: ty, + value, + span: span(), + }) +} + +pub fn const_define_globally(name: &str, ty: Type, value: Expr) -> GlobalStmt { + GlobalStmt::Const(Constant { + name: name.to_string(), + type_name: ty, + value, + span: span(), + }) +} + +pub fn var_decl(explicitly_initialized: bool, name: &str, ty: Type, value: Expr) -> Stmt { + Stmt::VarDecl(VariableDeclaration { + name: name.to_string(), + type_name: ty, + value, + explicitly_initialized, + span: span(), + }) +} + + +pub fn var_assign(name: &str, value: Expr) -> Stmt { + Stmt::VarAssign(VariableAssignment { + name: name.to_string(), + value, + span: span(), + }) +} + +pub fn contains_array_literal(expr: &Expr) -> bool { + match expr { + Expr::ArrayLiteral { .. } => true, + Expr::BinOp { left, right, .. } => { + contains_array_literal(left) || contains_array_literal(right) + } + _ => false, + } +} + + +pub fn array_lit(exprs: Vec, type_name: Option) -> Expr { + Expr::ArrayLiteral { elements: exprs, type_name, span: span() } +} + +pub fn int8_lit(n: i8) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Int8(n), span: span() } +} + +pub fn int16_lit(n: i16) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Int16(n), span: span() } +} + +pub fn int32_lit(n: i32) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Int32(n), span: span() } +} + +pub fn int64_lit(n: i64) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Int64(n), span: span() } +} + +pub fn int128_lit(n: i128) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Int128(n), span: span() } +} + + + +pub fn byte_lit(b: u8) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Byte(b), span: span() } +} + +pub fn uint16_lit(n: u16) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Uint16(n), span: span() } +} + +pub fn uint32_lit(n: u32) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Uint32(n), span: span() } +} + +pub fn uint64_lit(n: u64) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Uint64(n), span: span() } +} + +pub fn uint128_lit(n: u128) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Uint128(n), span: span() } +} + + +pub fn usize_lit(n: usize) -> Expr { + Expr::IntLiteral { value: IntLiteralValue::Usize(n), span: span() } +} + + + +pub fn float64_lit(f: f64) -> Expr { + Expr::Float64Literal { value: f, span: span() } +} + + +pub fn bool_lit(b: bool) -> Expr { + Expr::BoolLiteral { value: b, span: span() } +} + +pub fn char_lit(c: char) -> Expr { + Expr::CharLiteral { value: c, span: span() } +} + +pub fn str_lit(s: &str) -> Expr { + Expr::StringLiteral { value: s.to_string(), span: span() } +} + +pub fn var_expr(name: &str) -> Expr { + Expr::Var { name: name.to_string(), span: span() } +} + +pub fn call_expr(name: &str, args: Vec) -> Expr { + Expr::Call { name: name.to_string(), args, span: span() } +} + +pub fn return_stmt(exprs: Vec) -> Stmt { + Stmt::Return(exprs) +} + From 29ca2d8c290e88573a1f681afccd45a63b17d8fe Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sun, 2 Aug 2026 02:39:33 +0300 Subject: [PATCH 11/15] tests: Add whitebox branch analysis unit tests for empty branch analysis --- .../branch_analysis/branch_analysis_tests.rs | 2 +- .../empty_branch_analysis_tests.rs | 61 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/semantic/branch_analysis/branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests.rs index b6efd1e..0ee3a7e 100644 --- a/src/semantic/branch_analysis/branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests.rs @@ -3,7 +3,7 @@ use super::*; use crate::semantic_test_helpers::*; use crate::ast::{ - InfiniteStmt + InfiniteStmt, WhileStmt }; mod empty_branch_analysis_tests; diff --git a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs index 8522316..a82c24e 100644 --- a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs @@ -16,11 +16,70 @@ mod empty_branch_analysis_tests { } #[test] - fn empty_infinite_statement() { + fn empty_infinite_stmt_errors() { for i in 1..1000 { let result = empty_branch_analysis_hazmat(&vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span() }); i]); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); } } + + #[test] + fn empty_nested_infinite_stmt_errors() { + let mut stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + + #[test] + fn empty_infinite_stmt_inside_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span() }) + ], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let result = empty_branch_analysis_hazmat(&vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }); i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } } From 9d155efed1832fb5fcc051bf88bfb58a733f9889 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sun, 16 Aug 2026 11:08:17 +0300 Subject: [PATCH 12/15] refactor: General code clean-up --- src/semantic.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/semantic.rs b/src/semantic.rs index 137eb5d..cd29cf2 100644 --- a/src/semantic.rs +++ b/src/semantic.rs @@ -1,3 +1,15 @@ +/// This file is mainly responsible for analyzing function bodies for semantics analysis, such as: +/// 1. Ensure statements are legal (i.e. a break statement must be in a loop, etc) +/// +/// and +/// 2. Tracking ownership (variable moves, copying, etc) to ensure legality (i.e. moved +/// variables cannot be used unless re-declared, etc) +/// and +/// 3. Internally coercing integer types if possible depending on the context +/// +/// P.S. No, these comments are not AI +/// + use std::collections::HashMap; use crate::error::GoldError; @@ -174,7 +186,7 @@ fn check_const( // Validate the constant type against the expression, AND internally coerce literals if - // possible (i.e. int8 -> int32, etc), AND validate the constant value expression + // possible (i.e. int8 becomes int32, etc), AND validate the constant value expression // to ensure it is known at compile-time, AND evaluate it, and then fold it. // constants::eval_const_expr_and_fold_it(cons, storage, fun_sigs)?; @@ -208,8 +220,6 @@ fn check_stmts( in_loop: bool ) -> Result<(), GoldError> { - - // Special rule: Despite fact you cannot lock/unlock variables declared upstream, // and function arguments are considered declared upstream, the // special rule, if we are not in a nested scope (i.e. the block of From 64fa277a2a3fbf8f9d454f9b7774c46cb0e87c23 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sun, 16 Aug 2026 11:09:51 +0300 Subject: [PATCH 13/15] refactor: Code clean-up and new unit tests for empty branch analysis --- src/semantic/branch_analysis.rs | 4 +- .../branch_analysis/branch_analysis_tests.rs | 2 +- .../empty_branch_analysis_tests.rs | 1485 ++++++++++++++++- src/semantic_test_helpers.rs | 1 - 4 files changed, 1487 insertions(+), 5 deletions(-) diff --git a/src/semantic/branch_analysis.rs b/src/semantic/branch_analysis.rs index ee22bdf..41d9716 100644 --- a/src/semantic/branch_analysis.rs +++ b/src/semantic/branch_analysis.rs @@ -8,6 +8,8 @@ /// 3. Analyzing return branches to ensure branches correctly return, or infinitely loops /// without breaking /// +/// P.S. No, these comments are not AI +/// use super::{ Stmt, GoldError, @@ -96,7 +98,7 @@ fn empty_branch_analysis_hazmat( Stmt::If(if_stmt) => { if if_stmt.if_branch.is_empty() { return Err(GoldError::Semantic(format!( - "If statement main branch has no statements. Empty branches are not allowed (line {} column {})", + "If statement `main` branch has no statements. Empty branches are not allowed (line {} column {})", if_stmt.span.line, if_stmt.span.column ))) } diff --git a/src/semantic/branch_analysis/branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests.rs index 0ee3a7e..17f8e4f 100644 --- a/src/semantic/branch_analysis/branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests.rs @@ -3,7 +3,7 @@ use super::*; use crate::semantic_test_helpers::*; use crate::ast::{ - InfiniteStmt, WhileStmt + InfiniteStmt, WhileStmt, ForStmt, IfStmt }; mod empty_branch_analysis_tests; diff --git a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs index a82c24e..eda8c71 100644 --- a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs @@ -44,7 +44,7 @@ mod empty_branch_analysis_tests { let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span() }) ], span: span() }); - + for _ in 1..=100 { stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![stmt], span: span() }); @@ -55,6 +55,213 @@ mod empty_branch_analysis_tests { } } + #[test] + fn empty_infinite_stmt_inside_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span() }) + ], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: var_expr("a"), branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_infinite_stmt_in_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()})], + elif_branches: vec![], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_infinite_stmt_in_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()})]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + + #[test] + fn empty_infinite_stmt_in_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()})]); i], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_infinite_stmt_in_if_stmt_main_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()})], + elif_branches: vec![], + else_branch: Some(vec![Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()})]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_infinite_stmt_if_stmt_main_and_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) ], + elif_branches: vec![(l.clone(), vec![ Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) ]); i], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_infinite_stmt_in_if_stmt_elif_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![ Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) ])], + else_branch: Some(vec![ Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) ]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_infinite_stmt_in_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ + Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) + ], elif_branches: vec![], else_branch: None, span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Infinite(InfiniteStmt{ branch: vec![ stmt ], span: span()}) ], + elif_branches: vec![], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_infinite_stmt_in_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![ + Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) + ]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_infinite_stmt_in_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![ + Stmt::Infinite(InfiniteStmt{ branch: vec![], span: span()}) + ]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("Infinite loop branch has no statements")); + } + } + } + + + // Same tests as above, except this time it's for While loop statements + // #[test] fn empty_while_stmt_errors() { let literals = get_all_literals(); @@ -72,7 +279,7 @@ mod empty_branch_analysis_tests { let literals = get_all_literals(); for l in literals { let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }); - + for _ in 1..=100 { stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![stmt], span: span() }); @@ -82,4 +289,1278 @@ mod empty_branch_analysis_tests { } } } + + + #[test] + fn empty_while_stmt_inside_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }) + ], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_inside_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }) + ], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_in_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })], + elif_branches: vec![], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_in_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + + #[test] + fn empty_while_stmt_in_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_in_if_stmt_main_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })], + elif_branches: vec![], + else_branch: Some(vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_if_stmt_main_and_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })], + elif_branches: vec![(l.clone(), vec![ Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_while_stmt_in_if_stmt_elif_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![ Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }) ])], + else_branch: Some(vec![ Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() })]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_while_stmt_in_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ + Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }) + ], elif_branches: vec![], else_branch: None, span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ stmt ], span: span() })], + elif_branches: vec![], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_while_stmt_in_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![ + Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }) + ]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_while_stmt_in_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![ + Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![], span: span() }) + ]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("While loop branch has no statements")); + } + } + } + + + + + // Same tests as above, except this time it's for For loop statements + // + #[test] + fn empty_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let result = empty_branch_analysis_hazmat(&vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }); i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_inside_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }) + ], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_inside_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }) + ], span: span() }); + + for _ in 1..=100 { + stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![stmt], span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_in_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })], + elif_branches: vec![], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_in_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + + #[test] + fn empty_for_stmt_in_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_in_if_stmt_main_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })], + elif_branches: vec![], + else_branch: Some(vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_if_stmt_main_and_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })], + elif_branches: vec![(l.clone(), vec![ Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_for_stmt_in_if_stmt_elif_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![ Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })])], + else_branch: Some(vec![ Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() })]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_for_stmt_in_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ + Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }) + ], elif_branches: vec![], else_branch: None, span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ stmt ], span: span() })], + elif_branches: vec![], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_for_stmt_in_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![ + Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }) + ]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + #[test] + fn empty_nested_for_stmt_in_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![ + Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![], span: span() }) + ]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("For loop branch has no statements")); + } + } + } + + + + + + + + // Similar tests as above, except this time it's for If statements + // + #[test] + fn empty_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: Some(vec![]), + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_elif_and_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![])], + else_branch: Some(vec![]), + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_if_stmt_main_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![], elif_branches: vec![], else_branch: None, span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ stmt ], + elif_branches: vec![], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_else_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![]), span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_elif_branch_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![(l.clone(), vec![])], else_branch: None, span: span()}); + + for _ in 1..=100 { + stmt = Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() + }); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + // + // Same as above tests, except, this time the if statement(s) are in an infinite loop statement + // branch + // + #[test] + fn empty_if_stmt_main_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_else_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + + #[test] + fn empty_if_stmt_elif_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_else_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_elif_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_elif_and_else_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![])], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_if_stmt_main_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![], elif_branches: vec![], else_branch: None, span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ stmt ], + elif_branches: vec![], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_else_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![]), span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_elif_branch_in_infinite_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![(l.clone(), vec![])], else_branch: None, span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::Infinite(InfiniteStmt{ branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + // + // Same as above tests, except, this time the if statement(s) are in a while loop statement + // branch + // + #[test] + fn empty_if_stmt_main_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_else_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + + #[test] + fn empty_if_stmt_elif_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_else_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_elif_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_elif_and_else_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![])], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_if_stmt_main_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![], elif_branches: vec![], else_branch: None, span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ stmt ], + elif_branches: vec![], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_else_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![]), span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_elif_branch_in_while_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![(l.clone(), vec![])], else_branch: None, span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::While(WhileStmt{ condition: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + // + // Same as above tests, except, this time the if statement(s) are in a for loop statement + // branch + // + #[test] + fn empty_if_stmt_main_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_else_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + + #[test] + fn empty_if_stmt_elif_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_else_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_main_and_elif_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![], + elif_branches: vec![(l.clone(), vec![]); i], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_if_stmt_elif_and_else_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + for i in 1..100 { + let stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], // dummy statement + elif_branches: vec![(l.clone(), vec![])], + else_branch: Some(vec![]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt; i]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + #[test] + fn empty_nested_if_stmt_main_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![], elif_branches: vec![], else_branch: None, span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ stmt ], + elif_branches: vec![], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `main` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_else_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![], else_branch: Some(vec![]), span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![], + else_branch: Some(vec![ stmt ]), + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `else` branch has no statements")); + } + } + } + + #[test] + fn empty_nested_if_stmt_elif_branch_in_for_stmt_errors() { + let literals = get_all_literals(); + for l in literals { + let mut stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ condition: l.clone(), if_branch: vec![ Stmt::Expr(l.clone())], elif_branches: vec![(l.clone(), vec![])], else_branch: None, span: span()}) + ], span: span()}); + + for _ in 1..=100 { + stmt = Stmt::For(ForStmt{ holder_name: "x".to_string(), value: l.clone(), branch: vec![ + Stmt::If(IfStmt{ + condition: l.clone(), + if_branch: vec![ Stmt::Expr(l.clone()) ], + elif_branches: vec![(l.clone(), vec![stmt])], + else_branch: None, + span: span() })], span: span()}); + + let result = empty_branch_analysis_hazmat(&vec![stmt.clone()]); + assert!(result.is_err()); + assert!(result.unwrap_err().to_string().contains("If statement `elif` branch has no statements")); + } + } + } + + + + + } diff --git a/src/semantic_test_helpers.rs b/src/semantic_test_helpers.rs index 3547414..47719c5 100644 --- a/src/semantic_test_helpers.rs +++ b/src/semantic_test_helpers.rs @@ -598,7 +598,6 @@ pub fn void_func(name: &str, params: Vec, mut body: Vec) -> Functio } } -/// Build a function that returns a single type. pub fn returning_func(name: &str, params: Vec, ret: Vec, body: Vec) -> Function { Function { name: name.to_string(), From b9728e1e6a364104fad05687a64258a92e28b7c4 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sun, 16 Aug 2026 11:13:09 +0300 Subject: [PATCH 14/15] tests: new empty branch analysis unit tests --- .../branch_analysis_tests/empty_branch_analysis_tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs index eda8c71..eed8146 100644 --- a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs @@ -771,9 +771,6 @@ mod empty_branch_analysis_tests { - - - // Similar tests as above, except this time it's for If statements // #[test] From 69853ff3d290451e00abce436c75607cf76067b7 Mon Sep 17 00:00:00 2001 From: ChadSec Date: Sun, 16 Aug 2026 11:19:00 +0300 Subject: [PATCH 15/15] tests: new empty branch analysis unit tests --- .../branch_analysis_tests/empty_branch_analysis_tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs index eed8146..46afed7 100644 --- a/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs +++ b/src/semantic/branch_analysis/branch_analysis_tests/empty_branch_analysis_tests.rs @@ -769,8 +769,6 @@ mod empty_branch_analysis_tests { } - - // Similar tests as above, except this time it's for If statements // #[test]