diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0b312d..1f2dbe73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## [next] - 2023-MM-DD + +### Fixed + +- **aiken-lang**: incorrect scoping for anonymous functions +- **aiken-lang**: duplicate arguments were allowed in anonymous functions + ## v1.0.0-alpha - 2023-04-13 ### Added diff --git a/crates/aiken-lang/src/tests/check.rs b/crates/aiken-lang/src/tests/check.rs index 244778b6..9125085b 100644 --- a/crates/aiken-lang/src/tests/check.rs +++ b/crates/aiken-lang/src/tests/check.rs @@ -154,6 +154,56 @@ fn multi_validator_warning() { )) } +#[test] +fn anonymous_function_scoping() { + let source_code = r#" + fn reduce(list, f, i) { + todo + } + + pub fn foo() { + let sum = + reduce( + [1, 2, 3], + fn(acc: Int, n: Int) { acc + n }, + 0, + ) + + sum + acc + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::UnknownVariable { name, .. })) if name == "acc" + )) +} + +#[test] +fn anonymous_function_dupicate_args() { + let source_code = r#" + fn reduce(list, f, i) { + todo + } + + pub fn foo() { + let sum = + reduce( + [1, 2, 3], + fn(acc: Int, acc: Int) { acc + acc }, + 0, + ) + + sum + } + "#; + + assert!(matches!( + check(parse(source_code)), + Err((_, Error::DuplicateArgument { label, .. })) if label == "acc" + )) +} + #[test] fn if_scoping() { let source_code = r#" diff --git a/crates/aiken-lang/src/tipo/expr.rs b/crates/aiken-lang/src/tipo/expr.rs index 6563837e..1d20f262 100644 --- a/crates/aiken-lang/src/tipo/expr.rs +++ b/crates/aiken-lang/src/tipo/expr.rs @@ -1480,32 +1480,45 @@ impl<'a, 'b> ExprTyper<'a, 'b> { ) -> Result<(Vec, TypedExpr), Error> { self.assert_no_assignment(&body)?; - for arg in &args { - match &arg.arg_name { - ArgName::Named { - name, - is_validator_param, - .. - } if !is_validator_param => { - self.environment.insert_variable( - name.to_string(), - ValueConstructorVariant::LocalVariable { - location: arg.location, - }, - arg.tipo.clone(), - ); + let (body_rigid_names, body_infer) = self.in_new_scope(|body_typer| { + let mut argument_names = HashMap::with_capacity(args.len()); - self.environment.init_usage( - name.to_string(), - EntityKind::Variable, - arg.location, - ); - } - ArgName::Named { .. } | ArgName::Discarded { .. } => (), - }; - } + for arg in &args { + match &arg.arg_name { + ArgName::Named { + name, + is_validator_param, + location, + .. + } if !is_validator_param => { + if let Some(duplicate_location) = argument_names.insert(name, location) { + return Err(Error::DuplicateArgument { + location: *location, + duplicate_location: *duplicate_location, + label: name.to_string(), + }); + } - let (body_rigid_names, body_infer) = (self.hydrator.rigid_names(), self.infer(body)); + body_typer.environment.insert_variable( + name.to_string(), + ValueConstructorVariant::LocalVariable { + location: arg.location, + }, + arg.tipo.clone(), + ); + + body_typer.environment.init_usage( + name.to_string(), + EntityKind::Variable, + arg.location, + ); + } + ArgName::Named { .. } | ArgName::Discarded { .. } => (), + }; + } + + Ok((body_typer.hydrator.rigid_names(), body_typer.infer(body))) + })?; let body = body_infer.map_err(|e| e.with_unify_error_rigid_names(&body_rigid_names))?;