diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 89c0b1d438e..399d9905269 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -3,8 +3,9 @@ use std::rc::Rc; use noirc_errors::Location; use crate::{ + ast::IntegerBitSize, hir::comptime::{errors::IResult, InterpreterError, Value}, - macros_api::NodeInterner, + macros_api::{NodeInterner, Signedness}, token::{SpannedToken, Token, Tokens}, QuotedType, Type, }; @@ -18,10 +19,16 @@ pub(super) fn call_builtin( match name { "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), + "is_unconstrained" => Ok(Value::Bool(true)), + "slice_insert" => slice_insert(interner, arguments, location), + "slice_pop_back" => slice_pop_back(interner, arguments, location), + "slice_pop_front" => slice_pop_front(interner, arguments, location), "slice_push_back" => slice_push_back(interner, arguments, location), + "slice_push_front" => slice_push_front(interner, arguments, location), + "slice_remove" => slice_remove(interner, arguments, location), "struct_def_as_type" => struct_def_as_type(interner, arguments, location), - "struct_def_generics" => struct_def_generics(interner, arguments, location), "struct_def_fields" => struct_def_fields(interner, arguments, location), + "struct_def_generics" => struct_def_generics(interner, arguments, location), _ => { let item = format!("Comptime evaluation for builtin function {name}"); Err(InterpreterError::Unimplemented { item, location }) @@ -42,6 +49,36 @@ fn check_argument_count( } } +fn failing_constraint(message: impl Into, location: Location) -> IResult { + let message = Some(Value::String(Rc::new(message.into()))); + Err(InterpreterError::FailingConstraint { message, location }) +} + +fn get_slice( + interner: &NodeInterner, + value: Value, + location: Location, +) -> IResult<(im::Vector, Type)> { + match value { + Value::Slice(values, typ) => Ok((values, typ)), + value => { + let type_var = Box::new(interner.next_type_variable()); + let expected = Type::Slice(type_var); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + +fn get_u32(value: Value, location: Location) -> IResult { + match value { + Value::U32(value) => Ok(value), + value => { + let expected = Type::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo); + Err(InterpreterError::TypeMismatch { expected, value, location }) + } + } +} + fn array_len( interner: &NodeInterner, mut arguments: Vec<(Value, Location)>, @@ -85,18 +122,9 @@ fn slice_push_back( check_argument_count(2, &arguments, location)?; let (element, _) = arguments.pop().unwrap(); - let (slice, _) = arguments.pop().unwrap(); - match slice { - Value::Slice(mut values, typ) => { - values.push_back(element); - Ok(Value::Slice(values, typ)) - } - value => { - let type_var = Box::new(interner.next_type_variable()); - let expected = Type::Slice(type_var); - Err(InterpreterError::TypeMismatch { expected, value, location }) - } - } + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.push_back(element); + Ok(Value::Slice(values, typ)) } /// fn as_type(self) -> Quoted @@ -198,3 +226,84 @@ fn struct_def_fields( ]))); Ok(Value::Slice(fields, typ)) } + +fn slice_remove( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(2, &arguments, location)?; + + let index = get_u32(arguments.pop().unwrap().0, location)? as usize; + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + + if values.is_empty() { + return failing_constraint("slice_remove called on empty slice", location); + } + + if index >= values.len() { + let message = format!( + "slice_remove: index {index} is out of bounds for a slice of length {}", + values.len() + ); + return failing_constraint(message, location); + } + + let element = values.remove(index); + Ok(Value::Tuple(vec![Value::Slice(values, typ), element])) +} + +fn slice_push_front( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(2, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.push_front(element); + Ok(Value::Slice(values, typ)) +} + +fn slice_pop_front( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(1, &arguments, location)?; + + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + match values.pop_front() { + Some(element) => Ok(Value::Tuple(vec![element, Value::Slice(values, typ)])), + None => failing_constraint("slice_pop_front called on empty slice", location), + } +} + +fn slice_pop_back( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(1, &arguments, location)?; + + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + match values.pop_back() { + Some(element) => Ok(Value::Tuple(vec![Value::Slice(values, typ), element])), + None => failing_constraint("slice_pop_back called on empty slice", location), + } +} + +fn slice_insert( + interner: &mut NodeInterner, + mut arguments: Vec<(Value, Location)>, + location: Location, +) -> Result { + check_argument_count(3, &arguments, location)?; + + let (element, _) = arguments.pop().unwrap(); + let index = get_u32(arguments.pop().unwrap().0, location)?; + let (mut values, typ) = get_slice(interner, arguments.pop().unwrap().0, location)?; + values.insert(index as usize, element); + Ok(Value::Slice(values, typ)) +} diff --git a/docs/docs/noir/standard_library/is_unconstrained.md b/docs/docs/noir/standard_library/is_unconstrained.md index bb157e719dc..51bb1bda8f1 100644 --- a/docs/docs/noir/standard_library/is_unconstrained.md +++ b/docs/docs/noir/standard_library/is_unconstrained.md @@ -56,4 +56,14 @@ pub fn external_interface(){ ``` -The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. \ No newline at end of file +The is_unconstrained result is resolved at compile time, so in unconstrained contexts the compiler removes the else branch, and in constrained contexts the compiler removes the if branch, reducing the amount of compute necessary to run external_interface. + +Note that using `is_unconstrained` in a `comptime` context will also return `true`: + +``` +fn main() { + comptime { + assert(is_unconstrained()); + } +} +``` diff --git a/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml b/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml new file mode 100644 index 00000000000..8ff281cf9e3 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_slice_methods/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_slice_methods" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] diff --git a/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr new file mode 100644 index 00000000000..b76e8d70b83 --- /dev/null +++ b/test_programs/compile_success_empty/comptime_slice_methods/src/main.nr @@ -0,0 +1,27 @@ +fn main() { + comptime + { + // Comptime scopes are counted as unconstrained + assert(std::runtime::is_unconstrained()); + + let slice = &[2]; + let slice = slice.push_back(3); + let slice = slice.push_front(1); + assert_eq(slice, &[1, 2, 3]); + + let slice = slice.insert(1, 10); + assert_eq(slice, &[1, 10, 2, 3]); + + let (slice, ten) = slice.remove(1); + assert_eq(slice, &[1, 2, 3]); + assert_eq(ten, 10); + + let (one, slice) = slice.pop_front(); + assert_eq(slice, &[2, 3]); + assert_eq(one, 1); + + let (slice, three) = slice.pop_back(); + assert_eq(slice, &[2]); + assert_eq(three, 3); + } +} diff --git a/tooling/nargo_cli/build.rs b/tooling/nargo_cli/build.rs index 0fbdaaba0b4..faac228cb63 100644 --- a/tooling/nargo_cli/build.rs +++ b/tooling/nargo_cli/build.rs @@ -61,13 +61,14 @@ const IGNORED_BRILLIG_TESTS: [&str; 11] = [ /// Certain features are only available in the elaborator. /// We skip these tests for non-elaborator code since they are not /// expected to work there. This can be removed once the old code is removed. -const IGNORED_NEW_FEATURE_TESTS: [&str; 6] = [ +const IGNORED_NEW_FEATURE_TESTS: [&str; 7] = [ "macros", "wildcard_type", "type_definition_annotation", "numeric_generics_explicit", "derive_impl", "comptime_traits", + "comptime_slice_methods", ]; fn read_test_cases(