diff --git a/crates/nargo_cli/tests/test_data/array_len/src/main.nr b/crates/nargo_cli/tests/test_data/array_len/src/main.nr index 9099cfa2144..2c3cc0aee60 100644 --- a/crates/nargo_cli/tests/test_data/array_len/src/main.nr +++ b/crates/nargo_cli/tests/test_data/array_len/src/main.nr @@ -1,14 +1,14 @@ use dep::std; -fn len_plus_1(array: [T]) -> Field { +fn len_plus_1(array: [T; N]) -> Field { array.len() + 1 } -fn add_lens(a: [T], b: [Field]) -> Field { +fn add_lens(a: [T; N], b: [Field; M]) -> Field { a.len() + b.len() } -fn nested_call(b: [Field]) -> Field { +fn nested_call(b: [Field; N]) -> Field { len_plus_1(b) } diff --git a/crates/nargo_cli/tests/test_data/brillig_slices/src/main.nr b/crates/nargo_cli/tests/test_data/brillig_slices/src/main.nr index 7e4e8729199..34a9afcd515 100644 --- a/crates/nargo_cli/tests/test_data/brillig_slices/src/main.nr +++ b/crates/nargo_cli/tests/test_data/brillig_slices/src/main.nr @@ -2,71 +2,75 @@ use dep::std::slice; use dep::std; unconstrained fn main(x: Field, y: Field) { - // Mark it as mut so the compiler doesn't simplify the following operations - // But don't reuse the mut slice variable until this is fixed https://github.com/noir-lang/noir/issues/1931 - let slice: [Field] = [y, x]; + let mut slice: [Field] = [y, x]; assert(slice.len() == 2); - let mut pushed_back_slice = slice.push_back(7); - assert(pushed_back_slice.len() == 3); - assert(pushed_back_slice[0] == y); - assert(pushed_back_slice[1] == x); - assert(pushed_back_slice[2] == 7); + slice = slice.push_back(7); + assert(slice.len() == 3); + assert(slice[0] == y); + assert(slice[1] == x); + assert(slice[2] == 7); // Array set on slice target - pushed_back_slice[0] = x; - pushed_back_slice[1] = y; - pushed_back_slice[2] = 1; - - assert(pushed_back_slice[0] == x); - assert(pushed_back_slice[1] == y); - assert(pushed_back_slice[2] == 1); - - assert(slice.len() == 2); - - let pushed_front_slice = pushed_back_slice.push_front(2); - assert(pushed_front_slice.len() == 4); - assert(pushed_front_slice[0] == 2); - assert(pushed_front_slice[1] == x); - assert(pushed_front_slice[2] == y); - assert(pushed_front_slice[3] == 1); - - let (item, popped_front_slice) = pushed_front_slice.pop_front(); + slice[0] = x; + slice[1] = y; + slice[2] = 1; + + assert(slice[0] == x); + assert(slice[1] == y); + assert(slice[2] == 1); + + slice = push_front_to_slice(slice, 2); + assert(slice.len() == 4); + assert(slice[0] == 2); + assert(slice[1] == x); + assert(slice[2] == y); + assert(slice[3] == 1); + + let (item, popped_front_slice) = slice.pop_front(); + slice = popped_front_slice; assert(item == 2); - assert(popped_front_slice.len() == 3); - assert(popped_front_slice[0] == x); - assert(popped_front_slice[1] == y); - assert(popped_front_slice[2] == 1); + assert(slice.len() == 3); + assert(slice[0] == x); + assert(slice[1] == y); + assert(slice[2] == 1); - let (popped_back_slice, another_item) = popped_front_slice.pop_back(); + let (popped_back_slice, another_item) = slice.pop_back(); + slice = popped_back_slice; assert(another_item == 1); - assert(popped_back_slice.len() == 2); - assert(popped_back_slice[0] == x); - assert(popped_back_slice[1] == y); + assert(slice.len() == 2); + assert(slice[0] == x); + assert(slice[1] == y); - let inserted_slice = popped_back_slice.insert(1, 2); - assert(inserted_slice.len() == 3); - assert(inserted_slice[0] == x); - assert(inserted_slice[1] == 2); - assert(inserted_slice[2] == y); + slice = slice.insert(1, 2); + assert(slice.len() == 3); + assert(slice[0] == x); + assert(slice[1] == 2); + assert(slice[2] == y); - let (removed_slice, should_be_2) = inserted_slice.remove(1); + let (removed_slice, should_be_2) = slice.remove(1); + slice = removed_slice; assert(should_be_2 == 2); - assert(removed_slice.len() == 2); - assert(removed_slice[0] == x); - assert(removed_slice[1] == y); + assert(slice.len() == 2); + assert(slice[0] == x); + assert(slice[1] == y); - let (slice_with_only_x, should_be_y) = removed_slice.remove(1); + let (slice_with_only_x, should_be_y) = slice.remove(1); + slice = slice_with_only_x; assert(should_be_y == y); - assert(slice_with_only_x.len() == 1); - assert(removed_slice[0] == x); + assert(slice.len() == 1); + assert(slice[0] == x); - let (empty_slice, should_be_x) = slice_with_only_x.remove(0); + let (empty_slice, should_be_x) = slice.remove(0); assert(should_be_x == x); assert(empty_slice.len() == 0); } +// Tests slice passing to/from functions +unconstrained fn push_front_to_slice(slice: [T], item: T) -> [T] { + slice.push_front(item) +} \ No newline at end of file diff --git a/crates/nargo_cli/tests/test_data/slices/src/main.nr b/crates/nargo_cli/tests/test_data/slices/src/main.nr index a0460aafb40..f97078a2143 100644 --- a/crates/nargo_cli/tests/test_data/slices/src/main.nr +++ b/crates/nargo_cli/tests/test_data/slices/src/main.nr @@ -4,7 +4,7 @@ fn main(x : Field, y : pub Field) { /// TODO(#1889): Using slices in if statements where the condition is a witness /// is not yet supported - let mut slice: [Field] = [0; 2]; + let mut slice = [0; 2]; assert(slice[0] == 0); assert(slice[0] != 1); slice[0] = x; @@ -15,7 +15,7 @@ fn main(x : Field, y : pub Field) { assert(slice_plus_10[2] != 8); assert(slice_plus_10.len() == 3); - let mut new_slice: [Field] = []; + let mut new_slice = []; for i in 0..5 { new_slice = new_slice.push_back(i); } diff --git a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs index 4de052aad9d..c7779533a8a 100644 --- a/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs +++ b/crates/noirc_evaluator/src/brillig/brillig_gen/brillig_block.rs @@ -800,10 +800,29 @@ impl<'block> BrilligBlock<'block> { value_id, dfg, ); - let heap_array = self.function_context.extract_heap_array(new_variable); - self.brillig_context - .allocate_fixed_length_array(heap_array.pointer, heap_array.size); + // Initialize the variable + let pointer = match new_variable { + RegisterOrMemory::HeapArray(heap_array) => { + self.brillig_context + .allocate_fixed_length_array(heap_array.pointer, array.len()); + + heap_array.pointer + } + RegisterOrMemory::HeapVector(heap_vector) => { + self.brillig_context + .const_instruction(heap_vector.size, array.len().into()); + self.brillig_context + .allocate_array_instruction(heap_vector.pointer, heap_vector.size); + + heap_vector.pointer + } + _ => unreachable!( + "ICE: Cannot initialize array value created as {new_variable:?}" + ), + }; + + // Write the items // Allocate a register for the iterator let iterator_register = self.brillig_context.make_constant(0_usize.into()); @@ -811,11 +830,7 @@ impl<'block> BrilligBlock<'block> { for element_id in array.iter() { let element_variable = self.convert_ssa_value(*element_id, dfg); // Store the item in memory - self.store_variable_in_array( - heap_array.pointer, - iterator_register, - element_variable, - ); + self.store_variable_in_array(pointer, iterator_register, element_variable); // Increment the iterator self.brillig_context.usize_op_in_place(iterator_register, BinaryIntOp::Add, 1); } diff --git a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs index b0ade9419fe..3bf18a2d86a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/acir_gen/mod.rs @@ -1057,7 +1057,8 @@ mod tests { let one = builder.field_constant(FieldElement::one()); let element_type = Rc::new(vec![Type::field()]); - let array = builder.array_constant(im::Vector::unit(one), element_type); + let array_type = Type::Array(element_type, 1); + let array = builder.array_constant(im::Vector::unit(one), array_type); builder.terminate_with_return(vec![array]); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs index 5c9fde280a8..caf65c85a7e 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/dfg.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, rc::Rc}; +use std::{borrow::Cow, collections::HashMap}; use crate::ssa_refactor::ir::instruction::SimplifyResult; @@ -9,7 +9,7 @@ use super::{ Instruction, InstructionId, InstructionResultType, Intrinsic, TerminatorInstruction, }, map::DenseMap, - types::{CompositeType, Type}, + types::Type, value::{Value, ValueId}, }; @@ -226,12 +226,9 @@ impl DataFlowGraph { } /// Create a new constant array value from the given elements - pub(crate) fn make_array( - &mut self, - array: im::Vector, - element_type: Rc, - ) -> ValueId { - self.make_value(Value::Array { array, element_type }) + pub(crate) fn make_array(&mut self, array: im::Vector, typ: Type) -> ValueId { + assert!(matches!(typ, Type::Array(..) | Type::Slice(_))); + self.make_value(Value::Array { array, typ }) } /// Gets or creates a ValueId for the given FunctionId. @@ -369,27 +366,19 @@ impl DataFlowGraph { /// Returns the Value::Array associated with this ValueId if it refers to an array constant. /// Otherwise, this returns None. - pub(crate) fn get_array_constant( - &self, - value: ValueId, - ) -> Option<(im::Vector, Rc)> { + pub(crate) fn get_array_constant(&self, value: ValueId) -> Option<(im::Vector, Type)> { match &self.values[self.resolve(value)] { // Vectors are shared, so cloning them is cheap - Value::Array { array, element_type } => Some((array.clone(), element_type.clone())), + Value::Array { array, typ } => Some((array.clone(), typ.clone())), _ => None, } } - /// Returns the Type::Array associated with this ValueId if it refers to an array parameter. - /// Otherwise, this returns None. - pub(crate) fn get_array_parameter_type( - &self, - value: ValueId, - ) -> Option<(Rc, usize)> { - match &self.values[self.resolve(value)] { - Value::Param { typ: Type::Array(element_type, size), .. } => { - Some((element_type.clone(), *size)) - } + /// If this value is an array, return the length of the array as indicated by its type. + /// Otherwise, return None. + pub(crate) fn try_get_array_length(&self, value: ValueId) -> Option { + match self.type_of_value(value) { + Type::Array(_, length) => Some(length), _ => None, } } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs index 22a1399ae79..38dcfbbb168 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/function_inserter.rs @@ -33,11 +33,11 @@ impl<'f> FunctionInserter<'f> { match self.values.get(&value) { Some(value) => *value, None => match &self.function.dfg[value] { - super::value::Value::Array { array, element_type } => { + super::value::Value::Array { array, typ } => { let array = array.clone(); - let element_type = element_type.clone(); + let typ = typ.clone(); let new_array = array.iter().map(|id| self.resolve(*id)).collect(); - let new_id = self.function.dfg.make_array(new_array, element_type); + let new_id = self.function.dfg.make_array(new_array, typ); self.values.insert(value, new_id); new_id } diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs index 416c53ba6b4..b7a3ea02ae9 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/instruction.rs @@ -420,15 +420,9 @@ fn simplify_call(func: ValueId, arguments: &[ValueId], dfg: &mut DataFlowGraph) Intrinsic::ArrayLen => { let slice = dfg.get_array_constant(arguments[0]); if let Some((slice, _)) = slice { - let slice_len = - dfg.make_constant(FieldElement::from(slice.len() as u128), Type::field()); - SimplifiedTo(slice_len) - } else if let Some((_, slice_len)) = dfg.get_array_parameter_type(arguments[0]) { - let slice_len = dfg.make_constant( - FieldElement::from(slice_len as u128), - Type::Numeric(NumericType::NativeField), - ); - SimplifiedTo(slice_len) + SimplifiedTo(dfg.make_constant((slice.len() as u128).into(), Type::field())) + } else if let Some(length) = dfg.try_get_array_length(arguments[0]) { + SimplifiedTo(dfg.make_constant((length as u128).into(), Type::field())) } else { None } @@ -534,9 +528,11 @@ fn constant_to_radix( while limbs.len() < limb_count_with_padding as usize { limbs.push(FieldElement::zero()); } - let result_constants = + let result_constants: im::Vector = limbs.into_iter().map(|limb| dfg.make_constant(limb, Type::unsigned(bit_size))).collect(); - dfg.make_array(result_constants, Rc::new(vec![Type::unsigned(bit_size)])) + + let typ = Type::Array(Rc::new(vec![Type::unsigned(bit_size)]), result_constants.len()); + dfg.make_array(result_constants, typ) } /// The possible return values for Instruction::return_types diff --git a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs index 03475f5f514..cea526058b4 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ir/value.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use acvm::FieldElement; use crate::ssa_refactor::ir::basic_block::BasicBlockId; @@ -8,7 +6,7 @@ use super::{ function::FunctionId, instruction::{InstructionId, Intrinsic}, map::Id, - types::{CompositeType, Type}, + types::Type, }; pub(crate) type ValueId = Id; @@ -38,7 +36,7 @@ pub(crate) enum Value { NumericConstant { constant: FieldElement, typ: Type }, /// Represents a constant array value - Array { array: im::Vector, element_type: Rc }, + Array { array: im::Vector, typ: Type }, /// This Value refers to a function in the IR. /// Functions always have the type Type::Function. @@ -64,9 +62,7 @@ impl Value { Value::Instruction { typ, .. } => typ.clone(), Value::Param { typ, .. } => typ.clone(), Value::NumericConstant { typ, .. } => typ.clone(), - Value::Array { element_type, array } => { - Type::Array(element_type.clone(), array.len() / element_type.len()) - } + Value::Array { typ, .. } => typ.clone(), Value::Function { .. } => Type::Function, Value::Intrinsic { .. } => Type::Function, Value::ForeignFunction { .. } => Type::Function, diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs index 3c40e2a15c5..acf048595d7 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/constant_folding.rs @@ -92,6 +92,8 @@ impl Context { #[cfg(test)] mod test { + use std::rc::Rc; + use crate::ssa_refactor::{ ir::{ function::RuntimeType, @@ -176,8 +178,9 @@ mod test { let v0 = builder.add_parameter(Type::field()); let one = builder.field_constant(1u128); let v1 = builder.insert_binary(v0, BinaryOp::Add, one); - let arr = - builder.current_function.dfg.make_array(vec![v1].into(), vec![Type::field()].into()); + + let array_type = Type::Array(Rc::new(vec![Type::field()]), 1); + let arr = builder.current_function.dfg.make_array(vec![v1].into(), array_type); builder.terminate_with_return(vec![arr]); let ssa = builder.finish().fold_constants(); diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs index ac62071d6ee..4ff857f942f 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/flatten_cfg.rs @@ -131,10 +131,7 @@ //! v11 = mul v4, Field 12 //! v12 = add v10, v11 //! store v12 at v5 (new store) -use std::{ - collections::{BTreeMap, HashMap, HashSet}, - rc::Rc, -}; +use std::collections::{BTreeMap, HashMap, HashSet}; use acvm::FieldElement; use iter_extended::vecmap; @@ -148,7 +145,7 @@ use crate::ssa_refactor::{ function::Function, function_inserter::FunctionInserter, instruction::{BinaryOp, Instruction, InstructionId, TerminatorInstruction}, - types::{CompositeType, Type}, + types::Type, value::ValueId, }, ssa_gen::Ssa, @@ -393,14 +390,9 @@ impl<'f> Context<'f> { Type::Numeric(_) => { self.merge_numeric_values(then_condition, else_condition, then_value, else_value) } - Type::Array(element_types, len) => self.merge_array_values( - element_types, - len, - then_condition, - else_condition, - then_value, - else_value, - ), + typ @ Type::Array(_, _) => { + self.merge_array_values(typ, then_condition, else_condition, then_value, else_value) + } // TODO(#1889) Type::Slice(_) => panic!("Cannot return slices from an if expression"), Type::Reference => panic!("Cannot return references from an if expression"), @@ -413,8 +405,7 @@ impl<'f> Context<'f> { /// by creating a new array containing the result of self.merge_values for each element. fn merge_array_values( &mut self, - element_types: Rc, - len: usize, + typ: Type, then_condition: ValueId, else_condition: ValueId, then_value: ValueId, @@ -422,6 +413,11 @@ impl<'f> Context<'f> { ) -> ValueId { let mut merged = im::Vector::new(); + let (element_types, len) = match &typ { + Type::Array(elements, len) => (elements, *len), + _ => panic!("Expected array type"), + }; + for i in 0..len { for (element_index, element_type) in element_types.iter().enumerate() { let index = ((i * element_types.len() + element_index) as u128).into(); @@ -446,7 +442,7 @@ impl<'f> Context<'f> { } } - self.inserter.function.dfg.make_array(merged, element_types) + self.inserter.function.dfg.make_array(merged, typ) } /// Merge two numeric values a and b from separate basic blocks to a single value. This @@ -1333,8 +1329,10 @@ mod test { let b3 = builder.insert_block(); let element_type = Rc::new(vec![Type::field()]); + let array_type = Type::Array(element_type.clone(), 1); + let zero = builder.field_constant(0_u128); - let zero_array = builder.array_constant(im::Vector::unit(zero), element_type.clone()); + let zero_array = builder.array_constant(im::Vector::unit(zero), array_type); let i_zero = builder.numeric_constant(0_u128, Type::unsigned(32)); let pedersen = builder.import_intrinsic_id(Intrinsic::BlackBox(acvm::acir::BlackBoxFunc::Pedersen)); diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs index 430b52ce9f6..7aa2f9d176a 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/inlining.rs @@ -217,9 +217,9 @@ impl<'function> PerFunctionContext<'function> { Value::ForeignFunction(function) => { self.context.builder.import_foreign_function(function) } - Value::Array { array, element_type } => { + Value::Array { array, typ } => { let elements = array.iter().map(|value| self.translate_value(*value)).collect(); - self.context.builder.array_constant(elements, element_type.clone()) + self.context.builder.array_constant(elements, typ.clone()) } }; diff --git a/crates/noirc_evaluator/src/ssa_refactor/opt/mem2reg.rs b/crates/noirc_evaluator/src/ssa_refactor/opt/mem2reg.rs index 145ba25f5a5..15108abc490 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/opt/mem2reg.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/opt/mem2reg.rs @@ -212,10 +212,11 @@ mod tests { let two = builder.field_constant(FieldElement::one()); let element_type = Rc::new(vec![Type::field()]); - let array = builder.array_constant(vector![one, two], element_type.clone()); + let array_type = Type::Array(element_type, 2); + let array = builder.array_constant(vector![one, two], array_type.clone()); builder.insert_store(v0, array); - let v1 = builder.insert_load(v0, Type::Array(element_type, 2)); + let v1 = builder.insert_load(v0, array_type); let v2 = builder.insert_array_get(v1, one, Type::field()); builder.terminate_with_return(vec![v2]); diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs index d3d9e56b3af..02350d9ed17 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_builder/mod.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, rc::Rc}; +use std::borrow::Cow; use acvm::FieldElement; use noirc_errors::Location; @@ -17,7 +17,6 @@ use super::{ dfg::InsertInstructionResult, function::RuntimeType, instruction::{InstructionId, Intrinsic}, - types::CompositeType, }, ssa_gen::Ssa, }; @@ -115,12 +114,8 @@ impl FunctionBuilder { } /// Insert an array constant into the current function with the given element values. - pub(crate) fn array_constant( - &mut self, - elements: im::Vector, - element_types: Rc, - ) -> ValueId { - self.current_function.dfg.make_array(elements, element_types) + pub(crate) fn array_constant(&mut self, elements: im::Vector, typ: Type) -> ValueId { + self.current_function.dfg.make_array(elements, typ) } /// Returns the type of the given value. diff --git a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs index 13e67f26cc5..2b6db4e7586 100644 --- a/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs +++ b/crates/noirc_evaluator/src/ssa_refactor/ssa_gen/mod.rs @@ -2,8 +2,6 @@ mod context; mod program; mod value; -use std::rc::Rc; - pub(crate) use program::Ssa; use context::SharedContext; @@ -16,12 +14,7 @@ use self::{ value::{Tree, Values}, }; -use super::ir::{ - function::RuntimeType, - instruction::BinaryOp, - types::{CompositeType, Type}, - value::ValueId, -}; +use super::ir::{function::RuntimeType, instruction::BinaryOp, types::Type, value::ValueId}; /// Generates SSA for the given monomorphized program. /// @@ -115,8 +108,8 @@ impl<'a> FunctionContext<'a> { match literal { ast::Literal::Array(array) => { let elements = vecmap(&array.contents, |element| self.codegen_expression(element)); - let element_types = Self::convert_type(&array.element_type).flatten(); - self.codegen_array(elements, element_types) + let typ = Self::convert_non_tuple_type(&array.typ); + self.codegen_array(elements, typ) } ast::Literal::Integer(value, typ) => { let typ = Self::convert_non_tuple_type(typ); @@ -129,7 +122,8 @@ impl<'a> FunctionContext<'a> { let elements = vecmap(string.as_bytes(), |byte| { self.builder.numeric_constant(*byte as u128, Type::field()).into() }); - self.codegen_array(elements, vec![Type::char()]) + let typ = Self::convert_non_tuple_type(&ast::Type::String(elements.len() as u64)); + self.codegen_array(elements, typ) } } } @@ -143,7 +137,7 @@ impl<'a> FunctionContext<'a> { /// stored the same as the array [1, 2, 3, 4]. /// /// The value returned from this function is always that of the allocate instruction. - fn codegen_array(&mut self, elements: Vec, element_types: CompositeType) -> Values { + fn codegen_array(&mut self, elements: Vec, typ: Type) -> Values { let mut array = im::Vector::new(); for element in elements { @@ -153,7 +147,7 @@ impl<'a> FunctionContext<'a> { }); } - self.builder.array_constant(array, Rc::new(element_types)).into() + self.builder.array_constant(array, typ).into() } fn codegen_block(&mut self, block: &[Expression]) -> Values { diff --git a/crates/noirc_frontend/src/hir/resolution/resolver.rs b/crates/noirc_frontend/src/hir/resolution/resolver.rs index 27fa91a086b..29b3cc485d5 100644 --- a/crates/noirc_frontend/src/hir/resolution/resolver.rs +++ b/crates/noirc_frontend/src/hir/resolution/resolver.rs @@ -333,11 +333,12 @@ impl<'a> Resolver<'a> { UnresolvedType::FieldElement(comp_time) => Type::FieldElement(comp_time), UnresolvedType::Array(size, elem) => { let elem = Box::new(self.resolve_type_inner(*elem, new_variables)); - if size.is_none() { - return Type::Slice(elem); - } - let resolved_size = self.resolve_array_size(size, new_variables); - Type::Array(Box::new(resolved_size), elem) + let size = if size.is_none() { + Type::NotConstant + } else { + self.resolve_array_size(size, new_variables) + }; + Type::Array(Box::new(size), elem) } UnresolvedType::Expression(expr) => self.convert_expression_type(expr), UnresolvedType::Integer(comp_time, sign, bits) => Type::Integer(comp_time, sign, bits), @@ -780,6 +781,7 @@ impl<'a> Resolver<'a> { | Type::TypeVariable(_, _) | Type::Constant(_) | Type::NamedGeneric(_, _) + | Type::NotConstant | Type::Forall(_, _) => (), Type::Array(length, _) => { @@ -788,10 +790,6 @@ impl<'a> Resolver<'a> { } } - Type::Slice(typ) => { - Self::find_numeric_generics_in_type(typ, found); - } - Type::Tuple(fields) => { for field in fields { Self::find_numeric_generics_in_type(field, found); diff --git a/crates/noirc_frontend/src/hir/type_check/expr.rs b/crates/noirc_frontend/src/hir/type_check/expr.rs index a2ff1c23a63..2c6578944be 100644 --- a/crates/noirc_frontend/src/hir/type_check/expr.rs +++ b/crates/noirc_frontend/src/hir/type_check/expr.rs @@ -48,7 +48,7 @@ impl<'interner> TypeChecker<'interner> { .unwrap_or_else(|| self.interner.next_type_variable()); let arr_type = Type::Array( - Box::new(Type::Constant(arr.len() as u64)), + Box::new(Type::constant_variable(arr.len() as u64, self.interner)), Box::new(first_elem_type.clone()), ); @@ -78,6 +78,12 @@ impl<'interner> TypeChecker<'interner> { } HirLiteral::Array(HirArrayLiteral::Repeated { repeated_element, length }) => { let elem_type = self.check_expression(&repeated_element); + let length = match length { + Type::Constant(length) => { + Type::constant_variable(length, self.interner) + } + other => other, + }; Type::Array(Box::new(length), Box::new(elem_type)) } HirLiteral::Bool(_) => Type::Bool(CompTime::new(self.interner)), @@ -109,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) @@ -119,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 @@ -276,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), @@ -328,7 +336,6 @@ impl<'interner> TypeChecker<'interner> { // XXX: We can check the array bounds here also, but it may be better to constant fold first // and have ConstId instead of ExprId for constants Type::Array(_, base_type) => *base_type, - Type::Slice(base_type) => *base_type, Type::Error => Type::Error, typ => { let span = self.interner.expr_span(&index_expr.collection); @@ -400,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() { @@ -497,7 +504,7 @@ impl<'interner> TypeChecker<'interner> { let arg_type = self.check_expression(&arg); let span = self.interner.expr_span(expr_id); - self.make_subtype_of(&arg_type, ¶m_type, span, || { + self.make_subtype_of(&arg_type, ¶m_type, arg, || { TypeCheckError::TypeMismatch { expected_typ: param_type.to_string(), expr_typ: arg_type.to_string(), @@ -794,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 { @@ -804,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) { @@ -822,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/type_check/mod.rs b/crates/noirc_frontend/src/hir/type_check/mod.rs index a36c1ea67bc..26d0e36abf9 100644 --- a/crates/noirc_frontend/src/hir/type_check/mod.rs +++ b/crates/noirc_frontend/src/hir/type_check/mod.rs @@ -63,13 +63,17 @@ pub fn type_check_func(interner: &mut NodeInterner, func_id: FuncId) -> Vec TypeChecker<'interner> { &mut self, actual: &Type, expected: &Type, - span: Span, + expression: ExprId, make_error: impl FnOnce() -> TypeCheckError, ) { - actual.make_subtype_of(expected, span, &mut self.errors, make_error); + actual.make_subtype_with_coercions( + expected, + expression, + self.interner, + &mut self.errors, + make_error, + ); } } diff --git a/crates/noirc_frontend/src/hir/type_check/stmt.rs b/crates/noirc_frontend/src/hir/type_check/stmt.rs index 003334ade4e..3130a8616de 100644 --- a/crates/noirc_frontend/src/hir/type_check/stmt.rs +++ b/crates/noirc_frontend/src/hir/type_check/stmt.rs @@ -108,7 +108,7 @@ impl<'interner> TypeChecker<'interner> { }); let span = self.interner.expr_span(&assign_stmt.expression); - self.make_subtype_of(&expr_type, &lvalue_type, span, || { + self.make_subtype_of(&expr_type, &lvalue_type, assign_stmt.expression, || { TypeCheckError::TypeMismatchWithSource { rhs: expr_type.clone(), lhs: lvalue_type.clone(), @@ -192,7 +192,6 @@ impl<'interner> TypeChecker<'interner> { let typ = match result { Type::Array(_, elem_type) => *elem_type, - Type::Slice(elem_type) => *elem_type, Type::Error => Type::Error, other => { // TODO: Need a better span here @@ -260,7 +259,7 @@ impl<'interner> TypeChecker<'interner> { // Now check if LHS is the same type as the RHS // Importantly, we do not coerce any types implicitly let expr_span = self.interner.expr_span(&rhs_expr); - self.make_subtype_of(&expr_type, &annotated_type, expr_span, || { + self.make_subtype_of(&expr_type, &annotated_type, rhs_expr, || { TypeCheckError::TypeMismatch { expected_typ: annotated_type.to_string(), expr_typ: expr_type.to_string(), diff --git a/crates/noirc_frontend/src/hir_def/types.rs b/crates/noirc_frontend/src/hir_def/types.rs index 143e59f9434..6e1113345a8 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. @@ -21,9 +26,6 @@ pub enum Type { /// is either a type variable of some kind or a Type::Constant. Array(Box, Box), - /// Slice(E) is a slice with elements of type E. - Slice(Box), - /// A primitive integer type with the given sign, bit count, and whether it is known at compile-time. /// E.g. `u32` would be `Integer(CompTime::No(None), Unsigned, 32)` Integer(CompTime, Signedness, u32), @@ -81,6 +83,11 @@ pub enum Type { /// bind to an integer without special checks to bind it to a non-type. Constant(u64), + /// The type of a slice is an array of size NotConstant. + /// The size of an array literal is resolved to this if it ever uses operations + /// involving slices. + NotConstant, + /// The result of some type error. Remembering type errors as their own type variant lets /// us avoid issuing repeat type errors for the same item. For example, a lambda with /// an invalid type would otherwise issue a new error each time it is called @@ -275,12 +282,18 @@ pub enum BinaryTypeOperator { pub enum TypeVariableKind { /// Can bind to any type Normal, + /// A generic integer or field type. This is a more specific kind of TypeVariable /// that can only be bound to Type::Field, Type::Integer, or other polymorphic integers. /// This is the type of undecorated integer literals like `46`. Typing them in this way /// allows them to be polymorphic over the actual integer/field type used without requiring /// type annotations on each integer literal. IntegerOrField(CompTime), + + /// A potentially constant array size. This will only bind to itself, Type::NotConstant, or + /// Type::Constant(n) with a matching size. This defaults to Type::Constant(n) if still unbound + /// during monomorphization. + Constant(u64), } /// A TypeVariable is a mutable reference that is either @@ -544,12 +557,18 @@ impl Type { Type::TypeVariable(Shared::new(TypeBinding::Unbound(id)), TypeVariableKind::Normal) } + /// Returns a TypeVariable(_, TypeVariableKind::Constant(length)) to bind to + /// a constant integer for e.g. an array length. + pub fn constant_variable(length: u64, interner: &mut NodeInterner) -> Type { + let id = interner.next_type_variable_id(); + let kind = TypeVariableKind::Constant(length); + Type::TypeVariable(Shared::new(TypeBinding::Unbound(id)), kind) + } + pub fn polymorphic_integer(interner: &mut NodeInterner) -> Type { let id = interner.next_type_variable_id(); - Type::TypeVariable( - Shared::new(TypeBinding::Unbound(id)), - TypeVariableKind::IntegerOrField(CompTime::new(interner)), - ) + let kind = TypeVariableKind::IntegerOrField(CompTime::new(interner)); + Type::TypeVariable(Shared::new(TypeBinding::Unbound(id)), kind) } /// A bit of an awkward name for this function - this function returns @@ -595,14 +614,13 @@ impl Type { | Type::TypeVariable(_, _) | Type::Constant(_) | Type::NamedGeneric(_, _) + | Type::NotConstant | Type::Forall(_, _) => false, Type::Array(length, elem) => { elem.contains_numeric_typevar(target_id) || named_generic_id_matches_target(length) } - Type::Slice(elem) => elem.contains_numeric_typevar(target_id), - Type::Tuple(fields) => { fields.iter().any(|field| field.contains_numeric_typevar(target_id)) } @@ -642,12 +660,18 @@ impl std::fmt::Display for Type { Type::FieldElement(comp_time) => { write!(f, "{comp_time}Field") } - Type::Array(len, typ) => write!(f, "[{typ}; {len}]"), - Type::Slice(typ) => write!(f, "[{typ}]"), + Type::Array(len, typ) => { + if matches!(len.follow_bindings(), Type::NotConstant) { + write!(f, "[{typ}]") + } else { + write!(f, "[{typ}; {len}]") + } + } Type::Integer(comp_time, sign, num_bits) => match sign { Signedness::Signed => write!(f, "{comp_time}i{num_bits}"), Signedness::Unsigned => write!(f, "{comp_time}u{num_bits}"), }, + Type::TypeVariable(id, TypeVariableKind::Normal) => write!(f, "{}", id.borrow()), Type::TypeVariable(binding, TypeVariableKind::IntegerOrField(_)) => { if let TypeBinding::Unbound(_) = &*binding.borrow() { // Show a Field by default if this TypeVariableKind::IntegerOrField is unbound, since that is @@ -658,6 +682,14 @@ impl std::fmt::Display for Type { write!(f, "{}", binding.borrow()) } } + Type::TypeVariable(binding, TypeVariableKind::Constant(n)) => { + if let TypeBinding::Unbound(_) = &*binding.borrow() { + // TypeVariableKind::Constant(n) binds to Type::Constant(n) by default, so just show that. + write!(f, "{n}") + } else { + write!(f, "{}", binding.borrow()) + } + } Type::Struct(s, args) => { let args = vecmap(args, |arg| arg.to_string()); if args.is_empty() { @@ -674,7 +706,6 @@ impl std::fmt::Display for Type { Type::String(len) => write!(f, "str<{len}>"), Type::Unit => write!(f, "()"), Type::Error => write!(f, "error"), - Type::TypeVariable(id, TypeVariableKind::Normal) => write!(f, "{}", id.borrow()), Type::NamedGeneric(binding, name) => match &*binding.borrow() { TypeBinding::Bound(binding) => binding.fmt(f), TypeBinding::Unbound(_) if name.is_empty() => write!(f, "_"), @@ -692,6 +723,7 @@ impl std::fmt::Display for Type { Type::MutableReference(element) => { write!(f, "&mut {element}") } + Type::NotConstant => write!(f, "_"), } } } @@ -770,6 +802,62 @@ impl Type { } } + /// Try to bind a MaybeConstant variable to self, succeeding if self is a Constant, + /// MaybeConstant, or type variable. + pub fn try_bind_to_maybe_constant( + &self, + var: &TypeVariable, + target_length: u64, + ) -> Result<(), SpanKind> { + let target_id = match &*var.borrow() { + TypeBinding::Bound(_) => unreachable!(), + TypeBinding::Unbound(id) => *id, + }; + + match self { + Type::Constant(length) if *length == target_length => { + *var.borrow_mut() = TypeBinding::Bound(self.clone()); + Ok(()) + } + Type::NotConstant => { + *var.borrow_mut() = TypeBinding::Bound(Type::NotConstant); + Ok(()) + } + Type::TypeVariable(binding, kind) => { + let borrow = binding.borrow(); + match &*borrow { + TypeBinding::Bound(typ) => typ.try_bind_to_maybe_constant(var, target_length), + // Avoid infinitely recursive bindings + TypeBinding::Unbound(id) if *id == target_id => Ok(()), + TypeBinding::Unbound(_) => match kind { + TypeVariableKind::Normal => { + drop(borrow); + let clone = Type::TypeVariable( + var.clone(), + TypeVariableKind::Constant(target_length), + ); + *binding.borrow_mut() = TypeBinding::Bound(clone); + Ok(()) + } + TypeVariableKind::Constant(length) if *length == target_length => { + drop(borrow); + let clone = Type::TypeVariable( + var.clone(), + TypeVariableKind::Constant(target_length), + ); + *binding.borrow_mut() = TypeBinding::Bound(clone); + Ok(()) + } + TypeVariableKind::Constant(_) | TypeVariableKind::IntegerOrField(_) => { + Err(SpanKind::None) + } + }, + } + } + _ => Err(SpanKind::None), + } + } + /// Try to bind a PolymorphicInt variable to self, succeeding if self is an integer, field, /// other PolymorphicInt type, or type variable. If use_subtype is true, the CompTime fields /// of each will be checked via sub-typing rather than unification. @@ -930,11 +1018,13 @@ impl Type { /// any unified bindings are on success. fn try_unify(&self, other: &Type, span: Span) -> Result<(), SpanKind> { use Type::*; + use TypeVariableKind as Kind; + match (self, other) { (Error, _) | (_, Error) => Ok(()), - (TypeVariable(binding, TypeVariableKind::IntegerOrField(comptime)), other) - | (other, TypeVariable(binding, TypeVariableKind::IntegerOrField(comptime))) => { + (TypeVariable(binding, Kind::IntegerOrField(comptime)), other) + | (other, TypeVariable(binding, Kind::IntegerOrField(comptime))) => { // If it is already bound, unify against what it is bound to if let TypeBinding::Bound(link) = &*binding.borrow() { return link.try_unify(other, span); @@ -944,7 +1034,8 @@ impl Type { other.try_bind_to_polymorphic_int(binding, comptime, false, span) } - (TypeVariable(binding, _), other) | (other, TypeVariable(binding, _)) => { + (TypeVariable(binding, Kind::Normal), other) + | (other, TypeVariable(binding, Kind::Normal)) => { if let TypeBinding::Bound(link) = &*binding.borrow() { return link.try_unify(other, span); } @@ -952,13 +1043,20 @@ impl Type { other.try_bind_to(binding) } + (TypeVariable(binding, Kind::Constant(length)), other) + | (other, TypeVariable(binding, Kind::Constant(length))) => { + if let TypeBinding::Bound(link) = &*binding.borrow() { + return link.try_unify(other, span); + } + + other.try_bind_to_maybe_constant(binding, *length) + } + (Array(len_a, elem_a), Array(len_b, elem_b)) => { len_a.try_unify(len_b, span)?; elem_a.try_unify(elem_b, span) } - (Slice(elem_a), Slice(elem_b)) => elem_a.try_unify(elem_b, span), - (Tuple(elements_a), Tuple(elements_b)) => { if elements_a.len() != elements_b.len() { Err(SpanKind::None) @@ -1048,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(()), @@ -1072,14 +1223,14 @@ impl Type { other.try_bind_to_polymorphic_int(binding, comptime, false, span) } - (TypeVariable(binding, _), other) => { + (TypeVariable(binding, TypeVariableKind::Normal), other) => { if let TypeBinding::Bound(link) = &*binding.borrow() { return link.is_subtype_of(other, span); } other.try_bind_to(binding) } - (other, TypeVariable(binding, _)) => { + (other, TypeVariable(binding, TypeVariableKind::Normal)) => { if let TypeBinding::Bound(link) = &*binding.borrow() { return other.is_subtype_of(link, span); } @@ -1087,15 +1238,26 @@ impl Type { other.try_bind_to(binding) } + (TypeVariable(binding, TypeVariableKind::Constant(length)), other) => { + if let TypeBinding::Bound(link) = &*binding.borrow() { + return link.is_subtype_of(other, span); + } + + other.try_bind_to_maybe_constant(binding, *length) + } + (other, TypeVariable(binding, TypeVariableKind::Constant(length))) => { + if let TypeBinding::Bound(link) = &*binding.borrow() { + return other.is_subtype_of(link, span); + } + + other.try_bind_to_maybe_constant(binding, *length) + } + (Array(len_a, elem_a), Array(len_b, elem_b)) => { len_a.is_subtype_of(len_b, span)?; elem_a.is_subtype_of(elem_b, span) } - (Slice(elem_a), Slice(elem_b)) => elem_a.is_subtype_of(elem_b, span), - - (Array(_, elem_a), Slice(elem_b)) => elem_a.is_subtype_of(elem_b, span), - (Tuple(elements_a), Tuple(elements_b)) => { if elements_a.len() != elements_b.len() { Err(SpanKind::None) @@ -1188,13 +1350,14 @@ impl Type { /// If this type is a Type::Constant (used in array lengths), or is bound /// to a Type::Constant, return the constant as a u64. pub fn evaluate_to_u64(&self) -> Option { - match self { - Type::NamedGeneric(binding, _) | Type::TypeVariable(binding, _) => { - match &*binding.borrow() { - TypeBinding::Bound(binding) => binding.evaluate_to_u64(), - TypeBinding::Unbound(_) => None, - } + if let Some(binding) = self.get_inner_type_variable() { + if let TypeBinding::Bound(binding) = &*binding.borrow() { + return binding.evaluate_to_u64(); } + } + + match self { + Type::TypeVariable(_, TypeVariableKind::Constant(size)) => Some(*size), Type::Array(len, _elem) => len.evaluate_to_u64(), Type::Constant(x) => Some(*x), _ => None, @@ -1247,8 +1410,8 @@ impl Type { Type::NamedGeneric(..) => unreachable!(), Type::Forall(..) => unreachable!(), Type::Function(_, _) => unreachable!(), - Type::Slice(_) => unreachable!("slices cannot be used in the abi"), Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), + Type::NotConstant => unreachable!(), } } @@ -1330,10 +1493,6 @@ impl Type { let element = Box::new(element.substitute(type_bindings)); Type::Array(size, element) } - Type::Slice(element) => { - let element = Box::new(element.substitute(type_bindings)); - Type::Slice(element) - } Type::String(size) => { let size = Box::new(size.substitute(type_bindings)); Type::String(size) @@ -1374,6 +1533,7 @@ impl Type { | Type::Bool(_) | Type::Constant(_) | Type::Error + | Type::NotConstant | Type::Unit => self.clone(), } } @@ -1382,7 +1542,6 @@ impl Type { fn occurs(&self, target_id: TypeVariableId) -> bool { match self { Type::Array(len, elem) => len.occurs(target_id) || elem.occurs(target_id), - Type::Slice(element) => element.occurs(target_id), Type::String(len) => len.occurs(target_id), Type::Struct(_, generic_args) => generic_args.iter().any(|arg| arg.occurs(target_id)), Type::Tuple(fields) => fields.iter().any(|field| field.occurs(target_id)), @@ -1405,6 +1564,7 @@ impl Type { | Type::Bool(_) | Type::Constant(_) | Type::Error + | Type::NotConstant | Type::Unit => false, } } @@ -1421,7 +1581,6 @@ impl Type { Array(size, elem) => { Array(Box::new(size.follow_bindings()), Box::new(elem.follow_bindings())) } - Slice(elem) => Slice(Box::new(elem.follow_bindings())), String(size) => String(Box::new(size.follow_bindings())), Struct(def, args) => { let args = vecmap(args, |arg| arg.follow_bindings()); @@ -1446,13 +1605,44 @@ impl Type { // Expect that this function should only be called on instantiated types Forall(..) => unreachable!(), - FieldElement(_) | Integer(_, _, _) | Bool(_) | Constant(_) | Unit | Error => { - self.clone() - } + FieldElement(_) + | Integer(_, _, _) + | Bool(_) + | Constant(_) + | Unit + | Error + | NotConstant => self.clone(), } } } +/// 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 { @@ -1465,3 +1655,14 @@ impl BinaryTypeOperator { } } } + +impl TypeVariableKind { + /// Returns the default type this type variable should be bound to if it is still unbound + /// during monomorphization. + pub(crate) fn default_type(&self) -> Type { + match self { + TypeVariableKind::IntegerOrField(_) | TypeVariableKind::Normal => Type::field(None), + TypeVariableKind::Constant(length) => Type::Constant(*length), + } + } +} diff --git a/crates/noirc_frontend/src/monomorphization/ast.rs b/crates/noirc_frontend/src/monomorphization/ast.rs index 7cac2ed8e4f..488d05c6509 100644 --- a/crates/noirc_frontend/src/monomorphization/ast.rs +++ b/crates/noirc_frontend/src/monomorphization/ast.rs @@ -119,7 +119,7 @@ pub struct Cast { #[derive(Debug, Clone)] pub struct ArrayLiteral { pub contents: Vec, - pub element_type: Type, + pub typ: Type, } #[derive(Debug, Clone)] diff --git a/crates/noirc_frontend/src/monomorphization/mod.rs b/crates/noirc_frontend/src/monomorphization/mod.rs index d9ee9666e3c..bb0228091da 100644 --- a/crates/noirc_frontend/src/monomorphization/mod.rs +++ b/crates/noirc_frontend/src/monomorphization/mod.rs @@ -22,8 +22,7 @@ use crate::{ }, node_interner::{self, DefinitionKind, NodeInterner, StmtId}, token::Attribute, - CompTime, ContractFunctionType, FunctionKind, Type, TypeBinding, TypeBindings, - TypeVariableKind, + ContractFunctionType, FunctionKind, TypeBinding, TypeBindings, TypeVariableKind, }; use self::ast::{Definition, FuncId, Function, LocalId, Program}; @@ -270,7 +269,7 @@ impl<'interner> Monomorphizer<'interner> { HirExpression::Literal(HirLiteral::Array(array)) => match array { HirArrayLiteral::Standard(array) => self.standard_array(expr, array), HirArrayLiteral::Repeated { repeated_element, length } => { - self.repeated_array(repeated_element, length) + self.repeated_array(expr, repeated_element, length) } }, HirExpression::Literal(HirLiteral::Unit) => ast::Expression::Block(vec![]), @@ -355,25 +354,26 @@ impl<'interner> Monomorphizer<'interner> { array: node_interner::ExprId, array_elements: Vec, ) -> ast::Expression { - let element_type = - Self::convert_type(&unwrap_array_element_type(&self.interner.id_type(array))); + let typ = Self::convert_type(&self.interner.id_type(array)); let contents = vecmap(array_elements, |id| self.expr(id)); - ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, element_type })) + ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, typ })) } fn repeated_array( &mut self, + array: node_interner::ExprId, repeated_element: node_interner::ExprId, length: HirType, ) -> ast::Expression { - let element_type = Self::convert_type(&self.interner.id_type(repeated_element)); + let typ = Self::convert_type(&self.interner.id_type(array)); + let contents = self.expr(repeated_element); let length = length .evaluate_to_u64() .expect("Length of array is unknown when evaluating numeric generic"); let contents = vec![contents; length as usize]; - ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, element_type })) + ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents, typ })) } fn index(&mut self, id: node_interner::ExprId, index: HirIndexExpression) -> ast::Expression { @@ -590,33 +590,49 @@ impl<'interner> Monomorphizer<'interner> { HirType::Unit => ast::Type::Unit, HirType::Array(length, element) => { - let length = length.evaluate_to_u64().unwrap_or(0); - let element = Self::convert_type(element.as_ref()); - ast::Type::Array(length, Box::new(element)) - } + let element = Box::new(Self::convert_type(element.as_ref())); - HirType::Slice(element) => { - let element = Self::convert_type(element.as_ref()); - ast::Type::Slice(Box::new(element)) + if let Some(length) = length.evaluate_to_u64() { + ast::Type::Array(length, element) + } else { + ast::Type::Slice(element) + } } - HirType::TypeVariable(binding, _) | HirType::NamedGeneric(binding, _) => { + HirType::NamedGeneric(binding, _) => { if let TypeBinding::Bound(binding) = &*binding.borrow() { return Self::convert_type(binding); } - // Default any remaining unbound type variables to Field. + // Default any remaining unbound type variables. // This should only happen if the variable in question is unused // and within a larger generic type. // NOTE: Make sure to review this if there is ever type-directed dispatch, // like automatic solving of traits. It should be fine since it is strictly // after type checking, but care should be taken that it doesn't change which // impls are chosen. - *binding.borrow_mut() = - TypeBinding::Bound(HirType::FieldElement(CompTime::No(None))); + *binding.borrow_mut() = TypeBinding::Bound(HirType::field(None)); ast::Type::Field } + HirType::TypeVariable(binding, kind) => { + if let TypeBinding::Bound(binding) = &*binding.borrow() { + return Self::convert_type(binding); + } + + // Default any remaining unbound type variables. + // This should only happen if the variable in question is unused + // and within a larger generic type. + // NOTE: Make sure to review this if there is ever type-directed dispatch, + // like automatic solving of traits. It should be fine since it is strictly + // after type checking, but care should be taken that it doesn't change which + // impls are chosen. + let default = kind.default_type(); + let monomorphized_default = Self::convert_type(&default); + *binding.borrow_mut() = TypeBinding::Bound(default); + monomorphized_default + } + HirType::Struct(def, args) => { let fields = def.borrow().get_fields(args); let fields = vecmap(fields, |(_, field)| Self::convert_type(&field)); @@ -639,7 +655,10 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::MutableReference(Box::new(element)) } - HirType::Forall(_, _) | HirType::Constant(_) | HirType::Error => { + HirType::Forall(_, _) + | HirType::Constant(_) + | HirType::NotConstant + | HirType::Error => { unreachable!("Unexpected type {} found", typ) } } @@ -667,8 +686,12 @@ impl<'interner> Monomorphizer<'interner> { } } - self.try_evaluate_call(&func, &call.arguments, &return_type) - .unwrap_or(ast::Expression::Call(ast::Call { func, arguments, return_type, location })) + self.try_evaluate_call(&func, &return_type).unwrap_or(ast::Expression::Call(ast::Call { + func, + arguments, + return_type, + location, + })) } /// Adds a function argument that contains type metadata that is required to tell @@ -707,25 +730,12 @@ impl<'interner> Monomorphizer<'interner> { fn try_evaluate_call( &mut self, func: &ast::Expression, - arguments: &[node_interner::ExprId], result_type: &ast::Type, ) -> Option { if let ast::Expression::Ident(ident) = func { if let Definition::Builtin(opcode) = &ident.definition { // TODO(#1736): Move this builtin to the SSA pass return match opcode.as_str() { - "array_len" => { - let typ = self.interner.id_type(arguments[0]); - if let Type::Array(_, _) = typ { - let len = typ.evaluate_to_u64().unwrap(); - Some(ast::Expression::Literal(ast::Literal::Integer( - (len as u128).into(), - ast::Type::Field, - ))) - } else { - None - } - } "modulus_num_bits" => Some(ast::Expression::Literal(ast::Literal::Integer( (FieldElement::max_num_bits() as u128).into(), ast::Type::Field, @@ -755,17 +765,17 @@ impl<'interner> Monomorphizer<'interner> { } fn modulus_array_literal(&self, bytes: Vec, arr_elem_bits: u32) -> ast::Expression { + use ast::*; + let int_type = Type::Integer(crate::Signedness::Unsigned, arr_elem_bits); + let bytes_as_expr = vecmap(bytes, |byte| { - ast::Expression::Literal(ast::Literal::Integer( - (byte as u128).into(), - ast::Type::Integer(crate::Signedness::Unsigned, arr_elem_bits), - )) + Expression::Literal(Literal::Integer((byte as u128).into(), int_type.clone())) }); - let arr_literal = ast::ArrayLiteral { - contents: bytes_as_expr, - element_type: ast::Type::Integer(crate::Signedness::Unsigned, arr_elem_bits), - }; - ast::Expression::Literal(ast::Literal::Array(arr_literal)) + + let typ = Type::Array(bytes_as_expr.len() as u64, Box::new(int_type)); + + let arr_literal = ArrayLiteral { typ, contents: bytes_as_expr }; + Expression::Literal(Literal::Array(arr_literal)) } fn queue_function( @@ -906,7 +916,7 @@ impl<'interner> Monomorphizer<'interner> { let element = self.zeroed_value_of_type(element_type.as_ref()); ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents: vec![element; *length as usize], - element_type: element_type.as_ref().clone(), + typ: ast::Type::Array(*length, element_type.clone()), })) } ast::Type::String(length) => { @@ -921,7 +931,7 @@ impl<'interner> Monomorphizer<'interner> { ast::Type::Slice(element_type) => { ast::Expression::Literal(ast::Literal::Array(ast::ArrayLiteral { contents: vec![], - element_type: *element_type.clone(), + typ: ast::Type::Slice(element_type.clone()), })) } ast::Type::MutableReference(element) => { @@ -992,20 +1002,6 @@ fn unwrap_struct_type(typ: &HirType) -> Vec<(String, HirType)> { } } -fn unwrap_array_element_type(typ: &HirType) -> HirType { - match typ { - HirType::Array(_, elem) => *elem.clone(), - HirType::Slice(elem) => *elem.clone(), - HirType::TypeVariable(binding, TypeVariableKind::Normal) => match &*binding.borrow() { - TypeBinding::Bound(binding) => unwrap_array_element_type(binding), - TypeBinding::Unbound(_) => unreachable!(), - }, - other => { - unreachable!("unwrap_array_element_type: expected an array or slice, found {:?}", other) - } - } -} - fn perform_instantiation_bindings(bindings: &TypeBindings) { for (var, binding) in bindings.values() { *var.borrow_mut() = TypeBinding::Bound(binding.clone()); diff --git a/crates/noirc_frontend/src/node_interner.rs b/crates/noirc_frontend/src/node_interner.rs index fa2ce49ed11..f01c5f22a50 100644 --- a/crates/noirc_frontend/src/node_interner.rs +++ b/crates/noirc_frontend/src/node_interner.rs @@ -608,7 +608,6 @@ enum TypeMethodKey { /// accept only fields or integers, it is just that their names may not clash. FieldOrInt, Array, - Slice, Bool, String, Unit, @@ -622,7 +621,6 @@ fn get_type_method_key(typ: &Type) -> Option { match &typ { Type::FieldElement(_) => Some(FieldOrInt), Type::Array(_, _) => Some(Array), - Type::Slice(_) => Some(Slice), Type::Integer(_, _, _) => Some(FieldOrInt), Type::TypeVariable(_, TypeVariableKind::IntegerOrField(_)) => Some(FieldOrInt), Type::Bool(_) => Some(Bool), @@ -638,6 +636,7 @@ fn get_type_method_key(typ: &Type) -> Option { | Type::Forall(_, _) | Type::Constant(_) | Type::Error + | Type::NotConstant | Type::Struct(_, _) => None, } } diff --git a/noir_stdlib/src/array.nr b/noir_stdlib/src/array.nr index 9e44aa03fcc..db349317f91 100644 --- a/noir_stdlib/src/array.nr +++ b/noir_stdlib/src/array.nr @@ -22,6 +22,15 @@ impl [T; N] { a } + // Converts an array into a slice. + fn as_slice(self) -> [T] { + let mut slice = []; + for elem in self { + slice = slice.push_back(elem); + } + slice + } + // Apply a function to each element of an array, returning a new array // containing the mapped elements. fn map(self, f: fn(T) -> U) -> [U; N] { diff --git a/noir_stdlib/src/ecdsa_secp256k1.nr b/noir_stdlib/src/ecdsa_secp256k1.nr index efeceef5df2..c46380e1988 100644 --- a/noir_stdlib/src/ecdsa_secp256k1.nr +++ b/noir_stdlib/src/ecdsa_secp256k1.nr @@ -1,2 +1,2 @@ #[foreign(ecdsa_secp256k1)] -fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message_hash: [u8]) -> bool {} +fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message_hash: [u8; N]) -> bool {} diff --git a/noir_stdlib/src/ecdsa_secp256r1.nr b/noir_stdlib/src/ecdsa_secp256r1.nr index 44df07d3590..77744384f52 100644 --- a/noir_stdlib/src/ecdsa_secp256r1.nr +++ b/noir_stdlib/src/ecdsa_secp256r1.nr @@ -1,2 +1,2 @@ #[foreign(ecdsa_secp256r1)] -fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message_hash: [u8]) -> bool {} +fn verify_signature(_public_key_x : [u8; 32], _public_key_y : [u8; 32], _signature: [u8; 64], _message_hash: [u8; N]) -> bool {} diff --git a/noir_stdlib/src/hash/poseidon.nr b/noir_stdlib/src/hash/poseidon.nr index 416f740bbdf..cb1e34927b4 100644 --- a/noir_stdlib/src/hash/poseidon.nr +++ b/noir_stdlib/src/hash/poseidon.nr @@ -101,7 +101,7 @@ fn check_security(rate: Field, width: Field, security: Field) -> bool { } // A*x where A is an n x n matrix in row-major order and x an n-vector -fn apply_matrix(a: [Field], x: [Field; N]) -> [Field; N] { +fn apply_matrix(a: [Field; M], x: [Field; N]) -> [Field; N] { let mut y = x; for i in 0..x.len() { diff --git a/noir_stdlib/src/merkle.nr b/noir_stdlib/src/merkle.nr index 1f1a45ffe17..07588a52a5a 100644 --- a/noir_stdlib/src/merkle.nr +++ b/noir_stdlib/src/merkle.nr @@ -3,7 +3,7 @@ // XXX: In the future we can add an arity parameter // Returns the merkle root of the tree from the provided leaf, its hashpath, using a pedersen hash function. -fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field]) -> Field { +fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field; N]) -> Field { let n = hash_path.len(); let index_bits = index.to_le_bits(n as u32); let mut current = leaf; @@ -18,4 +18,4 @@ fn compute_merkle_root(leaf: Field, index: Field, hash_path: [Field]) -> Field { current = crate::hash::pedersen([hash_left, hash_right])[0]; }; current -} \ No newline at end of file +} diff --git a/noir_stdlib/src/schnorr.nr b/noir_stdlib/src/schnorr.nr index 5000efd3be4..1e69bcec821 100644 --- a/noir_stdlib/src/schnorr.nr +++ b/noir_stdlib/src/schnorr.nr @@ -1,2 +1,2 @@ #[foreign(schnorr_verify)] -fn verify_signature(_public_key_x: Field, _public_key_y: Field, _signature: [u8; 64], _message: [u8]) -> bool {} +fn verify_signature(_public_key_x: Field, _public_key_y: Field, _signature: [u8; 64], _message: [u8; N]) -> bool {} diff --git a/noir_stdlib/src/slice.nr b/noir_stdlib/src/slice.nr index 186d535a264..8e344a40f5e 100644 --- a/noir_stdlib/src/slice.nr +++ b/noir_stdlib/src/slice.nr @@ -32,74 +32,5 @@ impl [T] { /// the removed element #[builtin(slice_remove)] fn remove(_self: Self, _index: Field) -> (Self, T) { } - - #[builtin(array_len)] - fn len(_self: Self) -> comptime Field {} - - #[builtin(arraysort)] - fn sort(_self: Self) -> Self {} - - // Sort with a custom sorting function. - fn sort_via(mut a: Self, ordering: fn(T, T) -> bool) -> Self { - for i in 1 .. a.len() { - for j in 0..i { - if ordering(a[i], a[j]) { - let old_a_j = a[j]; - a[j] = a[i]; - a[i] = old_a_j; - } - } - } - a - } - - // Apply a function to each element of a slice, returning a new slice - // containing the mapped elements. - fn map(self, f: fn(T) -> U) -> [U] { - let mut ret: [U] = []; - for elem in self { - ret = ret.push_back(f(elem)); - } - ret - } - - // Apply a function to each element of the slice and an accumulator value, - // returning the final accumulated value. This function is also sometimes - // called `foldl`, `fold_left`, `reduce`, or `inject`. - fn fold(self, mut accumulator: U, f: fn(U, T) -> U) -> U { - for elem in self { - accumulator = f(accumulator, elem); - } - accumulator - } - - // Apply a function to each element of the slice and an accumulator value, - // returning the final accumulated value. Unlike fold, reduce uses the first - // element of the given slice as its starting accumulator value. - fn reduce(self, f: fn(T, T) -> T) -> T { - let mut accumulator = self[0]; - for i in 1 .. self.len() { - accumulator = f(accumulator, self[i]); - } - accumulator - } - - // Returns true if all elements in the array satisfy the predicate - fn all(self, predicate: fn(T) -> bool) -> bool { - let mut ret = true; - for elem in self { - ret &= predicate(elem); - } - ret - } - - // Returns true if any element in the array satisfies the predicate - fn any(self, predicate: fn(T) -> bool) -> bool { - let mut ret = false; - for elem in self { - ret |= predicate(elem); - } - ret - } }