Skip to content

Commit

Permalink
feat: Allow calling higher-order functions with closures (#2335)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexvitkov authored Aug 16, 2023
1 parent 8110136 commit 75fd3e0
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 29 deletions.
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 @@ -837,11 +837,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> {
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

0 comments on commit 75fd3e0

Please sign in to comment.