From f44e0b3ebfb30e9323ebf2d537830ea64d59488c Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Mon, 19 Aug 2024 15:54:07 -0300 Subject: [PATCH] feat: add `Expr::as_array`, `Expr::as_repeated_element_array` and same for slice (#5750) # Description ## Problem Part of #5668 ## Summary ## Additional Context I'm still not sure about the "repeated" name, and also about the similarity of the methods between array and slice (maybe we can distinguish array and slice with a boolean? not sure) but changing them should be easy, so let me know. ## Documentation Check one: - [ ] No documentation needed. - [ ] Documentation included in this PR. - [x] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --- .../src/hir/comptime/interpreter/builtin.rs | 184 +++++++++++----- noir_stdlib/src/meta/expr.nr | 196 ++++++++++++++++++ .../comptime_exp/Nargo.toml | 7 - .../comptime_exp/src/main.nr | 87 -------- 4 files changed, 328 insertions(+), 146 deletions(-) delete mode 100644 test_programs/compile_success_empty/comptime_exp/Nargo.toml delete mode 100644 test_programs/compile_success_empty/comptime_exp/src/main.nr diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index cec4b1fd434..f65c5ae8cdf 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -17,8 +17,8 @@ use rustc_hash::FxHashMap as HashMap; use crate::{ ast::{ - ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, Literal, UnaryOp, - UnresolvedType, UnresolvedTypeData, Visibility, + ArrayLiteral, ExpressionKind, FunctionKind, FunctionReturnType, IntegerBitSize, Literal, + UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, hir::comptime::{errors::IResult, value::add_token_spans, InterpreterError, Value}, hir_def::function::FunctionBody, @@ -47,6 +47,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "array_as_str_unchecked" => array_as_str_unchecked(interner, arguments, location), "array_len" => array_len(interner, arguments, location), "as_slice" => as_slice(interner, arguments, location), + "expr_as_array" => expr_as_array(arguments, return_type, location), "expr_as_binary_op" => expr_as_binary_op(arguments, return_type, location), "expr_as_bool" => expr_as_bool(arguments, return_type, location), "expr_as_function_call" => expr_as_function_call(arguments, return_type, location), @@ -54,8 +55,15 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_as_index" => expr_as_index(arguments, return_type, location), "expr_as_integer" => expr_as_integer(arguments, return_type, location), "expr_as_member_access" => expr_as_member_access(arguments, return_type, location), - "expr_as_unary_op" => expr_as_unary_op(arguments, return_type, location), + "expr_as_repeated_element_array" => { + expr_as_repeated_element_array(arguments, return_type, location) + } + "expr_as_repeated_element_slice" => { + expr_as_repeated_element_slice(arguments, return_type, location) + } + "expr_as_slice" => expr_as_slice(arguments, return_type, location), "expr_as_tuple" => expr_as_tuple(arguments, return_type, location), + "expr_as_unary_op" => expr_as_unary_op(arguments, return_type, location), "is_unconstrained" => Ok(Value::Bool(true)), "function_def_name" => function_def_name(interner, arguments, location), "function_def_parameters" => function_def_parameters(interner, arguments, location), @@ -758,6 +766,56 @@ fn zeroed(return_type: Type) -> IResult { } } +// fn as_array(self) -> Option<[Expr]> +fn expr_as_array( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr { + let exprs = exprs.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(exprs, typ)) + } else { + None + } + }) +} + +// fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> +fn expr_as_binary_op( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::Infix(infix_expr) = expr { + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 3); + + tuple_types.pop().unwrap(); + let binary_op_type = tuple_types.pop().unwrap(); + + // For the op value we use the enum member index, which should match noir_stdlib/src/meta/op.nr + let binary_op_value = infix_expr.operator.contents as u128; + + let mut fields = HashMap::default(); + fields.insert(Rc::new("op".to_string()), Value::Field(binary_op_value.into())); + + let unary_op = Value::Struct(fields, binary_op_type); + let lhs = Value::Expr(infix_expr.lhs.kind); + let rhs = Value::Expr(infix_expr.rhs.kind); + Some(Value::Tuple(vec![lhs, unary_op, rhs])) + } else { + None + } + }) +} + // fn as_bool(self) -> Option fn expr_as_bool( arguments: Vec<(Value, Location)>, @@ -872,70 +930,55 @@ fn expr_as_member_access( }) } -// fn as_unary_op(self) -> Option<(UnaryOp, Expr)> -fn expr_as_unary_op( +// fn as_repeated_element_array(self) -> Option<(Expr, Expr)> +fn expr_as_repeated_element_array( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - expr_as(arguments, return_type.clone(), location, |expr| { - if let ExpressionKind::Prefix(prefix_expr) = expr { - let option_type = extract_option_generic_type(return_type); - let Type::Tuple(mut tuple_types) = option_type else { - panic!("Expected the return type option generic arg to be a tuple"); - }; - assert_eq!(tuple_types.len(), 2); - - tuple_types.pop().unwrap(); - let unary_op_type = tuple_types.pop().unwrap(); - - // These values should match the values used in noir_stdlib/src/meta/op.nr - let unary_op_value: u128 = match prefix_expr.operator { - UnaryOp::Minus => 0, - UnaryOp::Not => 1, - UnaryOp::MutableReference => 2, - UnaryOp::Dereference { .. } => 3, - }; - - let mut fields = HashMap::default(); - fields.insert(Rc::new("op".to_string()), Value::Field(unary_op_value.into())); - - let unary_op = Value::Struct(fields, unary_op_type); - let rhs = Value::Expr(prefix_expr.rhs.kind); - Some(Value::Tuple(vec![unary_op, rhs])) + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Repeated { + repeated_element, + length, + })) = expr + { + Some(Value::Tuple(vec![Value::Expr(repeated_element.kind), Value::Expr(length.kind)])) } else { None } }) } -// fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> -fn expr_as_binary_op( +// fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> +fn expr_as_repeated_element_slice( arguments: Vec<(Value, Location)>, return_type: Type, location: Location, ) -> IResult { - expr_as(arguments, return_type.clone(), location, |expr| { - if let ExpressionKind::Infix(infix_expr) = expr { - let option_type = extract_option_generic_type(return_type); - let Type::Tuple(mut tuple_types) = option_type else { - panic!("Expected the return type option generic arg to be a tuple"); - }; - assert_eq!(tuple_types.len(), 3); - - tuple_types.pop().unwrap(); - let binary_op_type = tuple_types.pop().unwrap(); - - // For the op value we use the enum member index, which should match noir_stdlib/src/meta/op.nr - let binary_op_value = infix_expr.operator.contents as u128; - - let mut fields = HashMap::default(); - fields.insert(Rc::new("op".to_string()), Value::Field(binary_op_value.into())); + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Repeated { + repeated_element, + length, + })) = expr + { + Some(Value::Tuple(vec![Value::Expr(repeated_element.kind), Value::Expr(length.kind)])) + } else { + None + } + }) +} - let unary_op = Value::Struct(fields, binary_op_type); - let lhs = Value::Expr(infix_expr.lhs.kind); - let rhs = Value::Expr(infix_expr.rhs.kind); - Some(Value::Tuple(vec![lhs, unary_op, rhs])) +// fn as_slice(self) -> Option<[Expr]> +fn expr_as_slice( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type, location, |expr| { + if let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(exprs))) = expr { + let exprs = exprs.into_iter().map(|expr| Value::Expr(expr.kind)).collect(); + let typ = Type::Slice(Box::new(Type::Quoted(QuotedType::Expr))); + Some(Value::Slice(exprs, typ)) } else { None } @@ -959,6 +1002,43 @@ fn expr_as_tuple( }) } +// fn as_unary_op(self) -> Option<(UnaryOp, Expr)> +fn expr_as_unary_op( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + expr_as(arguments, return_type.clone(), location, |expr| { + if let ExpressionKind::Prefix(prefix_expr) = expr { + let option_type = extract_option_generic_type(return_type); + let Type::Tuple(mut tuple_types) = option_type else { + panic!("Expected the return type option generic arg to be a tuple"); + }; + assert_eq!(tuple_types.len(), 2); + + tuple_types.pop().unwrap(); + let unary_op_type = tuple_types.pop().unwrap(); + + // These values should match the values used in noir_stdlib/src/meta/op.nr + let unary_op_value: u128 = match prefix_expr.operator { + UnaryOp::Minus => 0, + UnaryOp::Not => 1, + UnaryOp::MutableReference => 2, + UnaryOp::Dereference { .. } => 3, + }; + + let mut fields = HashMap::default(); + fields.insert(Rc::new("op".to_string()), Value::Field(unary_op_value.into())); + + let unary_op = Value::Struct(fields, unary_op_type); + let rhs = Value::Expr(prefix_expr.rhs.kind); + Some(Value::Tuple(vec![unary_op, rhs])) + } else { + None + } + }) +} + // Helper function for implementing the `expr_as_...` functions. fn expr_as( arguments: Vec<(Value, Location)>, diff --git a/noir_stdlib/src/meta/expr.nr b/noir_stdlib/src/meta/expr.nr index 55c49531017..94889b7c3dd 100644 --- a/noir_stdlib/src/meta/expr.nr +++ b/noir_stdlib/src/meta/expr.nr @@ -3,6 +3,9 @@ use crate::meta::op::UnaryOp; use crate::meta::op::BinaryOp; impl Expr { + #[builtin(expr_as_array)] + fn as_array(self) -> Option<[Expr]> {} + #[builtin(expr_as_integer)] fn as_integer(self) -> Option<(Field, bool)> {} @@ -24,9 +27,202 @@ impl Expr { #[builtin(expr_as_member_access)] fn as_member_access(self) -> Option<(Expr, Quoted)> {} + #[builtin(expr_as_repeated_element_array)] + fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} + + #[builtin(expr_as_repeated_element_slice)] + fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} + + #[builtin(expr_as_slice)] + fn as_slice(self) -> Option<[Expr]> {} + #[builtin(expr_as_tuple)] fn as_tuple(self) -> Option<[Expr]> {} #[builtin(expr_as_unary_op)] fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} } + +mod tests { + use crate::meta::op::UnaryOp; + use crate::meta::op::BinaryOp; + + #[test] + fn test_expr_as_array() { + comptime + { + let expr = quote { [1, 2, 4] }.as_expr().unwrap(); + let elems = expr.as_array().unwrap(); + assert_eq(elems.len(), 3); + assert_eq(elems[0].as_integer().unwrap(), (1, false)); + assert_eq(elems[1].as_integer().unwrap(), (2, false)); + assert_eq(elems[2].as_integer().unwrap(), (4, false)); + } + } + + #[test] + fn test_expr_as_integer() { + comptime + { + let expr = quote { 1 }.as_expr().unwrap(); + assert_eq((1, false), expr.as_integer().unwrap()); + + let expr = quote { -2 }.as_expr().unwrap(); + assert_eq((2, true), expr.as_integer().unwrap()); + } + } + + #[test] + fn test_expr_as_binary_op() { + comptime + { + assert(get_binary_op(quote { x + y }).is_add()); + assert(get_binary_op(quote { x - y }).is_subtract()); + assert(get_binary_op(quote { x * y }).is_multiply()); + assert(get_binary_op(quote { x / y }).is_divide()); + assert(get_binary_op(quote { x == y }).is_equal()); + assert(get_binary_op(quote { x != y }).is_not_equal()); + assert(get_binary_op(quote { x > y }).is_greater()); + assert(get_binary_op(quote { x >= y }).is_greater_or_equal()); + assert(get_binary_op(quote { x & y }).is_and()); + assert(get_binary_op(quote { x | y }).is_or()); + assert(get_binary_op(quote { x ^ y }).is_xor()); + assert(get_binary_op(quote { x >> y }).is_shift_right()); + assert(get_binary_op(quote { x << y }).is_shift_left()); + assert(get_binary_op(quote { x % y }).is_modulo()); + } + } + + #[test] + fn test_expr_as_bool() { + comptime + { + let expr = quote { false }.as_expr().unwrap(); + assert(expr.as_bool().unwrap() == false); + + let expr = quote { true }.as_expr().unwrap(); + assert_eq(expr.as_bool().unwrap(), true); + } + } + + #[test] + fn test_expr_as_function_call() { + comptime + { + let expr = quote { foo(42) }.as_expr().unwrap(); + let (_function, args) = expr.as_function_call().unwrap(); + assert_eq(args.len(), 1); + assert_eq(args[0].as_integer().unwrap(), (42, false)); + } + } + + #[test] + fn test_expr_as_if() { + comptime + { + let expr = quote { if 1 { 2 } }.as_expr().unwrap(); + let (_condition, _consequence, alternative) = expr.as_if().unwrap(); + assert(alternative.is_none()); + + let expr = quote { if 1 { 2 } else { 3 } }.as_expr().unwrap(); + let (_condition, _consequence, alternative) = expr.as_if().unwrap(); + assert(alternative.is_some()); + } + } + + #[test] + fn test_expr_as_index() { + comptime + { + let expr = quote { foo[bar] }.as_expr().unwrap(); + assert(expr.as_index().is_some()); + } + } + + #[test] + fn test_expr_as_member_access() { + comptime + { + let expr = quote { foo.bar }.as_expr().unwrap(); + let (_, name) = expr.as_member_access().unwrap(); + assert_eq(name, quote { bar }); + } + } + + #[test] + fn test_expr_as_repeated_element_array() { + comptime + { + let expr = quote { [1; 3] }.as_expr().unwrap(); + let (expr, length) = expr.as_repeated_element_array().unwrap(); + assert_eq(expr.as_integer().unwrap(), (1, false)); + assert_eq(length.as_integer().unwrap(), (3, false)); + } + } + + #[test] + fn test_expr_as_repeated_element_slice() { + comptime + { + let expr = quote { &[1; 3] }.as_expr().unwrap(); + let (expr, length) = expr.as_repeated_element_slice().unwrap(); + assert_eq(expr.as_integer().unwrap(), (1, false)); + assert_eq(length.as_integer().unwrap(), (3, false)); + } + } + + #[test] + fn test_expr_as_slice() { + comptime + { + let expr = quote { &[1, 3, 5] }.as_expr().unwrap(); + let elems = expr.as_slice().unwrap(); + assert_eq(elems.len(), 3); + assert_eq(elems[0].as_integer().unwrap(), (1, false)); + assert_eq(elems[1].as_integer().unwrap(), (3, false)); + assert_eq(elems[2].as_integer().unwrap(), (5, false)); + } + } + + #[test] + fn test_expr_as_tuple() { + comptime + { + let expr = quote { (1, 2) }.as_expr().unwrap(); + let tuple_exprs = expr.as_tuple().unwrap(); + assert_eq(tuple_exprs.len(), 2); + } + } + + #[test] + fn test_expr_as_unary_op() { + comptime + { + assert(get_unary_op(quote { -x }).is_minus()); + assert(get_unary_op(quote { !x }).is_not()); + assert(get_unary_op(quote { &mut x }).is_mutable_reference()); + assert(get_unary_op(quote { *x }).is_dereference()); + } + } + + #[test] + fn test_automatically_unwraps_parenthesized_expression() { + comptime + { + let expr = quote { ((if 1 { 2 })) }.as_expr().unwrap(); + assert(expr.as_if().is_some()); + } + } + + comptime fn get_unary_op(quoted: Quoted) -> UnaryOp { + let expr = quoted.as_expr().unwrap(); + let (op, _) = expr.as_unary_op().unwrap(); + op + } + + comptime fn get_binary_op(quoted: Quoted) -> BinaryOp { + let expr = quoted.as_expr().unwrap(); + let (_, op, _) = expr.as_binary_op().unwrap(); + op + } +} diff --git a/test_programs/compile_success_empty/comptime_exp/Nargo.toml b/test_programs/compile_success_empty/comptime_exp/Nargo.toml deleted file mode 100644 index df36e0e05b0..00000000000 --- a/test_programs/compile_success_empty/comptime_exp/Nargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "comptime_exp" -type = "bin" -authors = [""] -compiler_version = ">=0.31.0" - -[dependencies] \ No newline at end of file diff --git a/test_programs/compile_success_empty/comptime_exp/src/main.nr b/test_programs/compile_success_empty/comptime_exp/src/main.nr deleted file mode 100644 index 7b25dcb5097..00000000000 --- a/test_programs/compile_success_empty/comptime_exp/src/main.nr +++ /dev/null @@ -1,87 +0,0 @@ -use std::meta::op::UnaryOp; -use std::meta::op::BinaryOp; - -fn main() { - comptime - { - // Check Expr::as_function_call - let expr = quote { foo(bar) }.as_expr().unwrap(); - let (_function, args) = expr.as_function_call().unwrap(); - assert_eq(args.len(), 1); - - // Check Expr::as_index - let expr = quote { foo[bar] }.as_expr().unwrap(); - let _ = expr.as_index().unwrap(); - - // Check Expr::as_tuple - let expr = quote { (1, 2) }.as_expr().unwrap(); - let tuple_exprs = expr.as_tuple().unwrap(); - assert_eq(tuple_exprs.len(), 2); - - // Check Expr::as_if - let expr = quote { if 1 { 2 } }.as_expr().unwrap(); - let (_condition, _consequence, alternative) = expr.as_if().unwrap(); - assert(alternative.is_none()); - - let expr = quote { if 1 { 2 } else { 3 } }.as_expr().unwrap(); - let (_condition, _consequence, alternative) = expr.as_if().unwrap(); - assert(alternative.is_some()); - - // Check parenthesized expression is automatically unwrapped - let expr = quote { ((if 1 { 2 })) }.as_expr().unwrap(); - assert(expr.as_if().is_some()); - - // Check Expr::as_bool - let expr = quote { false }.as_expr().unwrap(); - assert(expr.as_bool().unwrap() == false); - - let expr = quote { true }.as_expr().unwrap(); - assert_eq(expr.as_bool().unwrap(), true); - - // Check Expr::as_unary_op - assert(get_unary_op(quote { -x }).is_minus()); - assert(get_unary_op(quote { !x }).is_not()); - assert(get_unary_op(quote { &mut x }).is_mutable_reference()); - assert(get_unary_op(quote { *x }).is_dereference()); - - // Check Expr::as_binary_op - assert(get_binary_op(quote { x + y }).is_add()); - assert(get_binary_op(quote { x - y }).is_subtract()); - assert(get_binary_op(quote { x * y }).is_multiply()); - assert(get_binary_op(quote { x / y }).is_divide()); - assert(get_binary_op(quote { x == y }).is_equal()); - assert(get_binary_op(quote { x != y }).is_not_equal()); - assert(get_binary_op(quote { x > y }).is_greater()); - assert(get_binary_op(quote { x >= y }).is_greater_or_equal()); - assert(get_binary_op(quote { x & y }).is_and()); - assert(get_binary_op(quote { x | y }).is_or()); - assert(get_binary_op(quote { x ^ y }).is_xor()); - assert(get_binary_op(quote { x >> y }).is_shift_right()); - assert(get_binary_op(quote { x << y }).is_shift_left()); - assert(get_binary_op(quote { x % y }).is_modulo()); - - // Check Expr::as_integer - let expr = quote { 1 }.as_expr().unwrap(); - assert_eq((1, false), expr.as_integer().unwrap()); - - let expr = quote { -2 }.as_expr().unwrap(); - assert_eq((2, true), expr.as_integer().unwrap()); - - // Check Expr::as_member_access - let expr = quote { foo.bar }.as_expr().unwrap(); - let (_, name) = expr.as_member_access().unwrap(); - assert_eq(name, quote { bar }); - } -} - -comptime fn get_unary_op(quoted: Quoted) -> UnaryOp { - let expr = quoted.as_expr().unwrap(); - let (op, _) = expr.as_unary_op().unwrap(); - op -} - -comptime fn get_binary_op(quoted: Quoted) -> BinaryOp { - let expr = quoted.as_expr().unwrap(); - let (_, op, _) = expr.as_binary_op().unwrap(); - op -}