diff --git a/silverscript-lang/src/compiler.rs b/silverscript-lang/src/compiler.rs index 05c0536..008a5ea 100644 --- a/silverscript-lang/src/compiler.rs +++ b/silverscript-lang/src/compiler.rs @@ -13,6 +13,9 @@ use crate::ast::{ pub use crate::errors::{CompilerError, ErrorSpan}; use crate::span; +/// Prefix used for synthetic argument bindings during inline function expansion. +pub const SYNTHETIC_ARG_PREFIX: &str = "__arg"; + #[derive(Debug, Clone, Copy, Default)] pub struct CompileOptions { pub allow_yield: bool, @@ -927,6 +930,10 @@ fn compile_function<'i>( } } let mut env: HashMap> = constants.clone(); + // Remove any constructor/constant names that collide with function param names (prioritizing function parameters on name collision). + for param in &function.params { + env.remove(¶m.name); + } let mut builder = ScriptBuilder::new(); let mut yields: Vec = Vec::new(); @@ -1296,6 +1303,7 @@ fn compile_statement<'i>( let returns = compile_inline_call( name, args, + params, types, env, builder, @@ -1362,6 +1370,7 @@ fn compile_statement<'i>( let returns = compile_inline_call( name, args, + params, types, env, builder, @@ -1715,6 +1724,7 @@ fn compile_validate_output_state_statement( fn compile_inline_call<'i>( name: &str, args: &[Expr<'i>], + params: &HashMap, caller_types: &mut HashMap, caller_env: &mut HashMap>, builder: &mut ScriptBuilder, @@ -1752,9 +1762,15 @@ fn compile_inline_call<'i>( } let mut env: HashMap> = contract_constants.clone(); + // Copy the caller's __arg_ (function param) bindings into the new inline call's env, allowing nested synthetic argument chain. + for (name, value) in caller_env.iter() { + if name.starts_with(SYNTHETIC_ARG_PREFIX) { + env.insert(name.clone(), value.clone()); + } + } for (index, (param, arg)) in function.params.iter().zip(args.iter()).enumerate() { let resolved = resolve_expr(arg.clone(), caller_env, &mut HashSet::new())?; - let temp_name = format!("__arg_{name}_{index}"); + let temp_name = format!("{SYNTHETIC_ARG_PREFIX}_{name}_{index}"); let param_type_name = type_name_from_ref(¶m.type_ref); env.insert(temp_name.clone(), resolved.clone()); types.insert(temp_name.clone(), param_type_name.clone()); @@ -1785,7 +1801,6 @@ fn compile_inline_call<'i>( } let mut yields: Vec> = Vec::new(); - let params = HashMap::new(); let body_len = function.body.len(); for (index, stmt) in function.body.iter().enumerate() { if let Statement::Return { exprs, .. } = stmt { @@ -1803,7 +1818,7 @@ fn compile_inline_call<'i>( compile_statement( stmt, &mut env, - ¶ms, + params, &mut types, builder, options, @@ -1820,7 +1835,7 @@ fn compile_inline_call<'i>( } for (name, value) in env.iter() { - if name.starts_with("__arg_") { + if name.starts_with(SYNTHETIC_ARG_PREFIX) { if let Some(type_name) = types.get(name) { caller_types.entry(name.clone()).or_insert_with(|| type_name.clone()); } @@ -2112,7 +2127,7 @@ fn resolve_expr<'i>( let Expr { kind, span } = expr; match kind { ExprKind::Identifier(name) => { - if name.starts_with("__arg_") { + if name.starts_with(SYNTHETIC_ARG_PREFIX) { return Ok(Expr::new(ExprKind::Identifier(name), span)); } if let Some(value) = env.get(&name) { diff --git a/silverscript-lang/tests/compiler_tests.rs b/silverscript-lang/tests/compiler_tests.rs index 96f2302..8e6ea91 100644 --- a/silverscript-lang/tests/compiler_tests.rs +++ b/silverscript-lang/tests/compiler_tests.rs @@ -3559,3 +3559,62 @@ fn empty_array_statement_expr_evaluation_compiles_to_empty_array_data() { assert_eq!(compiled.script[0], OpFalse); assert_eq!(compiled.script[1], OpFalse); } + +#[test] +fn function_param_shadows_constructor_constant_with_same_name() { + // When a constructor constant and a function parameter share the same name, + // the function parameter value must be used (not the constant). + let source = r#" + contract Shadow(int fee) { + entrypoint function main(int fee) { + int local = fee + 1; + require(local == 4); + } + } + "#; + + // Constructor fee=2, param fee=3 => local = 3+1 = 4 => pass + let compiled = compile_contract(source, &[Expr::int(2)], CompileOptions::default()).expect("compile succeeds"); + let sigscript = compiled.build_sig_script("main", vec![Expr::int(3)]).expect("sigscript builds"); + let result = run_script_with_sigscript(compiled.script.clone(), sigscript); + assert!(result.is_ok(), "function param should shadow constructor constant: {}", result.unwrap_err()); + + // Constructor fee=2, param fee=2 => local = 2+1 = 3 != 4 => fail (proves it's not always the constant) + let sigscript_wrong = compiled.build_sig_script("main", vec![Expr::int(2)]).expect("sigscript builds"); + let result_wrong = run_script_with_sigscript(compiled.script, sigscript_wrong); + assert!(result_wrong.is_err(), "require(3==4) should fail, proving the param value matters"); +} + +#[test] +fn nested_inline_calls_with_args_compile_and_execute() { + // Nested inline calls must propagate synthetic __arg_ bindings so that + // deeply nested calls can resolve arguments that flow through outer calls. + let source = r#" + contract NestedArgs() { + function inner(int x) { + int y = x + 1; + require(y > 0); + } + + function outer(int v) { + inner(v); + require(v >= 0); + } + + function top(int z) { + outer(z); + require(z >= 0); + } + + entrypoint function main(int a) { + top(a); + require(a >= 0); + } + } + "#; + + let compiled = compile_contract(source, &[], CompileOptions::default()).expect("nested inline calls should compile"); + let sigscript = compiled.build_sig_script("main", vec![Expr::int(5)]).expect("sigscript builds"); + let result = run_script_with_sigscript(compiled.script, sigscript); + assert!(result.is_ok(), "nested inline calls should execute correctly: {}", result.unwrap_err()); +}