feat(aiken-lang): anonymous functions

@MartinSchere noticed a weird error
where an unknown variable wasn't being reported
the type checker was incorrectly scoping
arguments for anonymous function definitions.
Luckily his compilation failed due to a FreeUnique
error during code gen which is good. But this may
have been the source of other mysterious FreeUnique
errors.

I also noticed that anonymous function allowed
arguments with the same name to be defined.

`fn(arg, arg)`

This now returns an error.
This commit is contained in:
rvcas 2023-04-16 16:38:43 -04:00
parent 20edce2146
commit 98c61ca151
No known key found for this signature in database
GPG Key ID: C09B64E263F7D68C
3 changed files with 94 additions and 24 deletions

View File

@ -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

View File

@ -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#"

View File

@ -1480,32 +1480,45 @@ impl<'a, 'b> ExprTyper<'a, 'b> {
) -> Result<(Vec<TypedArg>, 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))?;