Skip to content

Commit

Permalink
Add array -> slice coercion
Browse files Browse the repository at this point in the history
  • Loading branch information
jfecher committed Jul 28, 2023
1 parent 3a3f242 commit c9efac9
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 18 deletions.
43 changes: 27 additions & 16 deletions crates/noirc_frontend/src/hir/type_check/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl<'interner> TypeChecker<'interner> {
let function = self.check_expression(&call_expr.func);
let args = vecmap(&call_expr.arguments, |arg| {
let typ = self.check_expression(arg);
(typ, self.interner.expr_span(arg))
(typ, *arg, self.interner.expr_span(arg))
});
let span = self.interner.expr_span(expr_id);
self.bind_function_type(function, args, span)
Expand All @@ -125,14 +125,16 @@ impl<'interner> TypeChecker<'interner> {
let method_name = method_call.method.0.contents.as_str();
match self.lookup_method(&object_type, method_name, expr_id) {
Some(method_id) => {
let mut args =
vec![(object_type, self.interner.expr_span(&method_call.object))];
let mut args = vec![(
object_type,
method_call.object,
self.interner.expr_span(&method_call.object),
)];

let mut arg_types = vecmap(&method_call.arguments, |arg| {
for arg in &method_call.arguments {
let typ = self.check_expression(arg);
(typ, self.interner.expr_span(arg))
});
args.append(&mut arg_types);
args.push((typ, *arg, self.interner.expr_span(arg)));
}

// Desugar the method call into a normal, resolved function call
// so that the backend doesn't need to worry about methods
Expand Down Expand Up @@ -282,7 +284,7 @@ impl<'interner> TypeChecker<'interner> {
&mut self,
method_call: &mut HirMethodCallExpression,
function_type: &Type,
argument_types: &mut [(Type, noirc_errors::Span)],
argument_types: &mut [(Type, ExprId, noirc_errors::Span)],
) {
let expected_object_type = match function_type {
Type::Function(args, _) => args.get(0),
Expand Down Expand Up @@ -405,7 +407,7 @@ impl<'interner> TypeChecker<'interner> {
&mut self,
function_ident_id: &ExprId,
func_id: &FuncId,
arguments: Vec<(Type, Span)>,
arguments: Vec<(Type, ExprId, Span)>,
span: Span,
) -> Type {
if func_id == &FuncId::dummy_id() {
Expand Down Expand Up @@ -799,7 +801,12 @@ impl<'interner> TypeChecker<'interner> {
}
}

fn bind_function_type(&mut self, function: Type, args: Vec<(Type, Span)>, span: Span) -> Type {
fn bind_function_type(
&mut self,
function: Type,
args: Vec<(Type, ExprId, Span)>,
span: Span,
) -> Type {
// Could do a single unification for the entire function type, but matching beforehand
// lets us issue a more precise error on the individual argument that fails to type check.
match function {
Expand All @@ -809,7 +816,7 @@ impl<'interner> TypeChecker<'interner> {
}

let ret = self.interner.next_type_variable();
let args = vecmap(args, |(arg, _)| arg);
let args = vecmap(args, |(arg, _, _)| arg);
let expected = Type::Function(args, Box::new(ret.clone()));

if let Err(error) = binding.borrow_mut().bind_to(expected, span) {
Expand All @@ -827,14 +834,18 @@ impl<'interner> TypeChecker<'interner> {
return Type::Error;
}

for (param, (arg, arg_span)) in parameters.iter().zip(args) {
arg.make_subtype_of(param, arg_span, &mut self.errors, || {
TypeCheckError::TypeMismatch {
for (param, (arg, arg_id, arg_span)) in parameters.iter().zip(args) {
arg.make_subtype_with_coercions(
param,
arg_id,
self.interner,
&mut self.errors,
|| TypeCheckError::TypeMismatch {
expected_typ: param.to_string(),
expr_typ: arg.to_string(),
expr_span: arg_span,
}
});
},
);
}

*ret
Expand Down
89 changes: 87 additions & 2 deletions crates/noirc_frontend/src/hir_def/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ use std::{
rc::Rc,
};

use crate::{hir::type_check::TypeCheckError, node_interner::NodeInterner};
use crate::{
hir::type_check::TypeCheckError,
node_interner::{ExprId, NodeInterner},
};
use iter_extended::vecmap;
use noirc_abi::AbiType;
use noirc_errors::Span;

use crate::{node_interner::StructId, Ident, Signedness};

use super::expr::{HirCallExpression, HirExpression, HirIdent};

#[derive(Debug, PartialEq, Eq, Clone, Hash)]
pub enum Type {
/// A primitive Field type, and whether or not it is known at compile-time.
Expand Down Expand Up @@ -1141,7 +1146,60 @@ impl Type {
}
}

fn is_subtype_of(&self, other: &Type, span: Span) -> Result<(), SpanKind> {
/// Similar to `make_subtype_of` 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()`
///
/// Currently the only type coercion in Noir is `[T; N]` into `[T]` via `.as_slice()`.
pub fn make_subtype_with_coercions(
&self,
expected: &Type,
expression: ExprId,
interner: &mut NodeInterner,
errors: &mut Vec<TypeCheckError>,
make_error: impl FnOnce() -> TypeCheckError,
) {
let span = interner.expr_span(&expression);
if let Err(err_span) = self.is_subtype_of(expected, span) {
if !self.try_array_to_slice_coercion(expected, expression, span, interner) {
Self::issue_errors(expected, err_span, errors, make_error);
}
}
}

/// Try to apply the array to slice coercion to this given type pair and expression.
/// If self can be converted to target this way, do so and return true to indicate success.
fn try_array_to_slice_coercion(
&self,
target: &Type,
expression: ExprId,
span: Span,
interner: &mut NodeInterner,
) -> bool {
let this = self.follow_bindings();
let target = target.follow_bindings();

if let (Type::Array(size1, element1), Type::Array(size2, element2)) = (&this, &target) {
let size1 = size1.follow_bindings();
let size2 = size2.follow_bindings();

// 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
if element1.is_subtype_of(&element2, span).is_ok() {
convert_array_expression_to_slice(expression, this, target, interner);
return true;
}
}
}
false
}

/// Checks if self is a subtype of `other`. Returns Ok(()) if it is and Err(_) if not.
/// Note that this function may permanently bind type variables regardless of whether it
/// returned Ok or Err.
pub fn is_subtype_of(&self, other: &Type, span: Span) -> Result<(), SpanKind> {
use Type::*;
match (self, other) {
(Error, _) | (_, Error) => Ok(()),
Expand Down Expand Up @@ -1558,6 +1616,33 @@ impl Type {
}
}

/// Wraps a given `expression` in `expression.as_slice()`
fn convert_array_expression_to_slice(
expression: ExprId,
array_type: Type,
target_type: Type,
interner: &mut NodeInterner,
) {
let as_slice_method = interner
.lookup_primitive_method(&array_type, "as_slice")
.expect("Expected 'as_slice' method to be present in Noir's stdlib");

let as_slice_id = interner.function_definition_id(as_slice_method);
let location = interner.expr_location(&expression);
let as_slice = HirExpression::Ident(HirIdent { location, id: as_slice_id });
let func = interner.push_expr(as_slice);

let arguments = vec![expression];
let call = HirExpression::Call(HirCallExpression { func, arguments, location });
let call = interner.push_expr(call);

interner.push_expr_location(call, location.span, location.file);
interner.push_expr_location(func, location.span, location.file);

interner.push_expr_type(&call, target_type.clone());
interner.push_expr_type(&func, Type::Function(vec![array_type], Box::new(target_type)));
}

impl BinaryTypeOperator {
/// Return the actual rust numeric function associated with this operator
pub fn function(self) -> fn(u64, u64) -> u64 {
Expand Down

0 comments on commit c9efac9

Please sign in to comment.