diff --git a/src/negative_tests.rs b/src/negative_tests.rs index dd557cf71..438015292 100644 --- a/src/negative_tests.rs +++ b/src/negative_tests.rs @@ -472,6 +472,29 @@ fn test_generic_custom_type_mismatched() { )); } +#[test] +fn test_generic_mutated_cst_var_in_loop() { + let code = r#" + fn gen(const LEN: Field) -> [Field; LEN] { + return [0; LEN]; + } + + fn main(pub xx: Field) { + let mut loopvar = 1; + for ii in 0..3 { + loopvar = loopvar + 1; + } + let arr = gen(loopvar); + } + "#; + + let res = tast_pass(code).0; + assert!(matches!( + res.unwrap_err().kind, + ErrorKind::ArgumentTypeMismatch(..) + )); +} + #[test] fn test_array_bounds() { let code = r#" diff --git a/src/type_checker/checker.rs b/src/type_checker/checker.rs index 150072bdd..4385d5be8 100644 --- a/src/type_checker/checker.rs +++ b/src/type_checker/checker.rs @@ -261,6 +261,10 @@ impl TypeChecker { .compute_type(lhs, typed_fn_env)? .expect("type-checker bug: lhs access on an empty var"); + if let Some(var_name) = &lhs_node.var_name { + typed_fn_env.invalidate_cst_var(var_name); + } + // todo: check and update the const field type for other cases // lhs can be a local variable or a path to an array let lhs_name = match &lhs.kind { diff --git a/src/type_checker/fn_env.rs b/src/type_checker/fn_env.rs index a8540cfd6..1bbf25113 100644 --- a/src/type_checker/fn_env.rs +++ b/src/type_checker/fn_env.rs @@ -110,6 +110,28 @@ impl TypedFnEnv { self.current_scope >= prefix_scope } + /// Because we don't support forloop unrolling, + /// we should invalidate the constant value behind a mutable variable, which is used in a forloop. + /// ```ignore + /// let mut pow2 = 1; + /// for ii in 0..LEN { + /// pow2 = pow2 + pow2; + /// } + /// ``` + /// Instead of folding the constant value to the mutable variable in this case, + /// the actual value will be calculated during synthesizer phase. + pub fn invalidate_cst_var(&mut self, ident: &str) { + // only applies to the variables in the parent scopes + // remove the constant value + if let Some((scope, info)) = self.vars.get_mut(ident) { + if scope < &mut self.current_scope + && matches!(info.typ, TyKind::Field { constant: true }) + { + info.typ = TyKind::Field { constant: false }; + } + } + } + /// Since currently we don't support unrolling, the generic function calls are assumed to target a same instance. /// Each loop iteration should instantiate generic function calls with the same parameters. /// This assumption requires a few type checking rules to forbid the cases that needs unrolling.