Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow calling higher-order functions with closures #2335

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions crates/noirc_frontend/src/hir/type_check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -835,11 +835,13 @@ impl<'interner> TypeChecker<'interner> {
}

for (param, (arg, _, arg_span)) in fn_params.iter().zip(callsite_args) {
self.unify(arg, param, || TypeCheckError::TypeMismatch {
expected_typ: param.to_string(),
expr_typ: arg.to_string(),
expr_span: *arg_span,
});
if arg.try_unify_allow_incompat_lambdas(param).is_err() {
self.errors.push(TypeCheckError::TypeMismatch {
expected_typ: param.to_string(),
expr_typ: arg.to_string(),
expr_span: *arg_span,
});
}
}

fn_ret.clone()
Expand Down
45 changes: 25 additions & 20 deletions crates/noirc_frontend/src/hir/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,28 +63,33 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec<Type
let (expr_span, empty_function) = function_info(interner, function_body_id);

let func_span = interner.expr_span(function_body_id); // XXX: We could be more specific and return the span of the last stmt, however stmts do not have spans yet
function_last_type.unify_with_coercions(
&declared_return_type,
*function_body_id,
interner,
&mut errors,
|| {
let mut error = TypeCheckError::TypeMismatchWithSource {
lhs: declared_return_type.clone(),
rhs: function_last_type.clone(),
span: func_span,
source: Source::Return(meta.return_type, expr_span),
};

if empty_function {
error = error.add_context(
"implicitly returns `()` as its body has no tail or `return` expression",
);
}
let result = function_last_type.try_unify_allow_incompat_lambdas(&declared_return_type);

if result.is_err() {
function_last_type.unify_with_coercions(
&declared_return_type,
*function_body_id,
interner,
&mut errors,
|| {
let mut error = TypeCheckError::TypeMismatchWithSource {
lhs: declared_return_type.clone(),
rhs: function_last_type.clone(),
span: func_span,
source: Source::Return(meta.return_type, expr_span),
};

if empty_function {
error = error.add_context(
"implicitly returns `()` as its body has no tail or `return` expression",
);
}

error
},
);
error
},
);
}
}

errors
Expand Down
36 changes: 32 additions & 4 deletions crates/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ pub enum Type {
/// TypeVariables are stand-in variables for some type which is not yet known.
/// They are not to be confused with NamedGenerics. While the later mostly works
/// as with normal types (ie. for two NamedGenerics T and U, T != U), TypeVariables
/// will be automatically rebound as necessary to satisfy any calls to unify
/// and make_subtype_of.
/// will be automatically rebound as necessary to satisfy any calls to unify.
///
/// TypeVariables are often created when a generic function is instantiated. This
/// is a process that replaces each NamedGeneric in a generic function with a TypeVariable.
Expand Down Expand Up @@ -885,7 +884,36 @@ impl Type {
}
}

/// Similar to `make_subtype_of` but if the check fails this will attempt to coerce the
/// Similar to try_unify() but allows non-matching capture groups for function types
pub fn try_unify_allow_incompat_lambdas(&self, other: &Type) -> Result<(), UnificationError> {
jfecher marked this conversation as resolved.
Show resolved Hide resolved
use Type::*;
use TypeVariableKind::*;

match (self, other) {
(TypeVariable(binding, Normal), other) | (other, TypeVariable(binding, Normal)) => {
if let TypeBinding::Bound(link) = &*binding.borrow() {
return link.try_unify_allow_incompat_lambdas(other);
}

other.try_bind_to(binding)
}
(Function(params_a, ret_a, _), Function(params_b, ret_b, _)) => {
if params_a.len() == params_b.len() {
for (a, b) in params_a.iter().zip(params_b.iter()) {
a.try_unify_allow_incompat_lambdas(b)?;
}

// no check for environments here!
ret_b.try_unify_allow_incompat_lambdas(ret_a)
} else {
Err(UnificationError)
}
}
_ => self.try_unify(other),
}
}

/// Similar to `unify` but if the check fails this will attempt to coerce the
/// argument to the target type. When this happens, the given expression is wrapped in
/// a new expression to convert its type. E.g. `array` -> `array.as_slice()`
///
Expand Down Expand Up @@ -923,7 +951,7 @@ impl Type {
// If we have an array and our target is a slice
if matches!(size1, Type::Constant(_)) && matches!(size2, Type::NotConstant) {
// Still have to ensure the element types match.
// Don't need to issue an error here if not, it will be done in make_subtype_of_with_coercions
// Don't need to issue an error here if not, it will be done in unify_with_coercions
if element1.try_unify(element2).is_ok() {
convert_array_expression_to_slice(expression, this, target, interner);
return true;
Expand Down