diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index 3caef11f47..70cc9c1ef1 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -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) @@ -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 @@ -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), @@ -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() { @@ -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 { @@ -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) { @@ -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 diff --git a/crates/noirc_frontend/src/hir_def/types.rs b/crates/noirc_frontend/src/hir_def/types.rs index 25d77bae6f..e3f0b62c05 100644 --- a/crates/noirc_frontend/src/hir_def/types.rs +++ b/crates/noirc_frontend/src/hir_def/types.rs @@ -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. @@ -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, + 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(()), @@ -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 {