From 76dea7b409baa98236f6433f17c2ce9206dd4ba3 Mon Sep 17 00:00:00 2001 From: Ary Borenszweig Date: Fri, 13 Sep 2024 14:21:41 -0300 Subject: [PATCH] feat: add `Expr::as_constructor` (#5980) # Description ## Problem Part of #5668 ## Summary Also add doc comments for `expr.nr`. ## Additional Context I wasn't sure how to represent a `Path`... so I did it with a `Quoted` value. I first thought about using a `Variable`, but then that wouldn't be parsed right when parsing a constructor, so it had to be some kind of Path. ## Documentation Check one: - [ ] No documentation needed. - [x] Documentation included in this PR. - [ ] **[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. --------- Co-authored-by: jfecher --- aztec_macros/src/transforms/storage.rs | 8 +- aztec_macros/src/utils/parse_utils.rs | 2 +- compiler/noirc_frontend/src/ast/expression.rs | 10 +- compiler/noirc_frontend/src/ast/mod.rs | 13 + compiler/noirc_frontend/src/ast/statement.rs | 2 +- compiler/noirc_frontend/src/ast/visitor.rs | 2 +- .../src/elaborator/expressions.rs | 33 +- .../noirc_frontend/src/elaborator/patterns.rs | 1 + .../noirc_frontend/src/hir/comptime/errors.rs | 4 +- .../src/hir/comptime/hir_to_display_ast.rs | 2 +- .../src/hir/comptime/interpreter/builtin.rs | 50 ++- .../interpreter/builtin/builtin_helpers.rs | 10 +- .../noirc_frontend/src/hir/comptime/value.rs | 2 +- .../src/hir/resolution/errors.rs | 2 +- compiler/noirc_frontend/src/parser/parser.rs | 7 +- docs/docs/noir/standard_library/meta/expr.md | 14 + noir_stdlib/src/meta/expr.nr | 308 +++++++----------- .../comptime_expr/src/main.nr | 29 ++ .../code_action/fill_struct_fields.rs | 12 +- tooling/lsp/src/requests/completion.rs | 17 +- tooling/nargo_fmt/src/rewrite/expr.rs | 6 +- 21 files changed, 301 insertions(+), 233 deletions(-) diff --git a/aztec_macros/src/transforms/storage.rs b/aztec_macros/src/transforms/storage.rs index a6bf2e14fb3..d056dfc4412 100644 --- a/aztec_macros/src/transforms/storage.rs +++ b/aztec_macros/src/transforms/storage.rs @@ -222,9 +222,11 @@ pub fn generate_storage_implementation( }) .collect(); - let storage_constructor_statement = make_statement(StatementKind::Expression(expression( - ExpressionKind::constructor((chained_path!(storage_struct_name), field_constructors)), - ))); + let storage_constructor_statement = + make_statement(StatementKind::Expression(expression(ExpressionKind::constructor(( + UnresolvedType::from_path(chained_path!(storage_struct_name)), + field_constructors, + ))))); // This is the type over which the impl is generic. let generic_context_ident = ident("Context"); diff --git a/aztec_macros/src/utils/parse_utils.rs b/aztec_macros/src/utils/parse_utils.rs index cdad842dd05..046ced105bf 100644 --- a/aztec_macros/src/utils/parse_utils.rs +++ b/aztec_macros/src/utils/parse_utils.rs @@ -505,7 +505,7 @@ fn empty_method_call_expression(method_call_expression: &mut MethodCallExpressio } fn empty_constructor_expression(constructor_expression: &mut ConstructorExpression) { - empty_path(&mut constructor_expression.type_name); + empty_unresolved_type(&mut constructor_expression.typ); for (name, expression) in constructor_expression.fields.iter_mut() { empty_ident(name); empty_expression(expression); diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index f242180134d..821750c8cb0 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -200,9 +200,11 @@ impl ExpressionKind { ExpressionKind::Literal(Literal::FmtStr(contents)) } - pub fn constructor((type_name, fields): (Path, Vec<(Ident, Expression)>)) -> ExpressionKind { + pub fn constructor( + (typ, fields): (UnresolvedType, Vec<(Ident, Expression)>), + ) -> ExpressionKind { ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name, + typ, fields, struct_type: None, })) @@ -536,7 +538,7 @@ pub struct MethodCallExpression { #[derive(Debug, PartialEq, Eq, Clone)] pub struct ConstructorExpression { - pub type_name: Path, + pub typ: UnresolvedType, pub fields: Vec<(Ident, Expression)>, /// This may be filled out during macro expansion @@ -717,7 +719,7 @@ impl Display for ConstructorExpression { let fields = self.fields.iter().map(|(ident, expr)| format!("{ident}: {expr}")).collect::>(); - write!(f, "({} {{ {} }})", self.type_name, fields.join(", ")) + write!(f, "({} {{ {} }})", self.typ, fields.join(", ")) } } diff --git a/compiler/noirc_frontend/src/ast/mod.rs b/compiler/noirc_frontend/src/ast/mod.rs index 12a8aec05eb..fe9153c2cf4 100644 --- a/compiler/noirc_frontend/src/ast/mod.rs +++ b/compiler/noirc_frontend/src/ast/mod.rs @@ -344,6 +344,19 @@ impl UnresolvedType { pub(crate) fn is_type_expression(&self) -> bool { matches!(&self.typ, UnresolvedTypeData::Expression(_)) } + + pub fn from_path(mut path: Path) -> Self { + let span = path.span; + let last_segment = path.segments.last_mut().unwrap(); + let generics = last_segment.generics.take(); + let generic_type_args = if let Some(generics) = generics { + GenericTypeArgs { ordered_args: generics, named_args: Vec::new() } + } else { + GenericTypeArgs::default() + }; + let typ = UnresolvedTypeData::Named(path, generic_type_args, true); + UnresolvedType { typ, span } + } } impl UnresolvedTypeData { diff --git a/compiler/noirc_frontend/src/ast/statement.rs b/compiler/noirc_frontend/src/ast/statement.rs index 52c39a49e8a..dbbb5212369 100644 --- a/compiler/noirc_frontend/src/ast/statement.rs +++ b/compiler/noirc_frontend/src/ast/statement.rs @@ -620,7 +620,7 @@ impl Pattern { } Some(Expression { kind: ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name: path.clone(), + typ: UnresolvedType::from_path(path.clone()), fields, struct_type: None, })), diff --git a/compiler/noirc_frontend/src/ast/visitor.rs b/compiler/noirc_frontend/src/ast/visitor.rs index 755b3bc670e..8f207a55e9a 100644 --- a/compiler/noirc_frontend/src/ast/visitor.rs +++ b/compiler/noirc_frontend/src/ast/visitor.rs @@ -948,7 +948,7 @@ impl ConstructorExpression { } pub fn accept_children(&self, visitor: &mut impl Visitor) { - self.type_name.accept(visitor); + self.typ.accept(visitor); for (_field_name, expression) in &self.fields { expression.accept(visitor); diff --git a/compiler/noirc_frontend/src/elaborator/expressions.rs b/compiler/noirc_frontend/src/elaborator/expressions.rs index 15d6ed5506b..0ab9a252f09 100644 --- a/compiler/noirc_frontend/src/elaborator/expressions.rs +++ b/compiler/noirc_frontend/src/elaborator/expressions.rs @@ -6,7 +6,7 @@ use rustc_hash::FxHashSet as HashSet; use crate::{ ast::{ ArrayLiteral, ConstructorExpression, IfExpression, InfixExpression, Lambda, - UnresolvedTypeExpression, + UnresolvedTypeData, UnresolvedTypeExpression, }, hir::{ comptime::{self, InterpreterError}, @@ -436,11 +436,29 @@ impl<'context> Elaborator<'context> { &mut self, constructor: ConstructorExpression, ) -> (HirExpression, Type) { + let span = constructor.typ.span; + + // A constructor type can either be a Path or an interned UnresolvedType. + // We represent both as UnresolvedType (with Path being a Named UnresolvedType) + // and error if we don't get a Named path. + let mut typ = constructor.typ.typ; + if let UnresolvedTypeData::Interned(id) = typ { + typ = self.interner.get_unresolved_type_data(id).clone(); + } + let UnresolvedTypeData::Named(mut path, generics, _) = typ else { + self.push_err(ResolverError::NonStructUsedInConstructor { typ: typ.to_string(), span }); + return (HirExpression::Error, Type::Error); + }; + + let last_segment = path.segments.last_mut().unwrap(); + if !generics.ordered_args.is_empty() { + last_segment.generics = Some(generics.ordered_args); + } + let exclude_last_segment = true; - self.check_unsupported_turbofish_usage(&constructor.type_name, exclude_last_segment); + self.check_unsupported_turbofish_usage(&path, exclude_last_segment); - let span = constructor.type_name.span(); - let last_segment = constructor.type_name.last_segment(); + let last_segment = path.last_segment(); let is_self_type = last_segment.ident.is_self_type_name(); let (r#type, struct_generics) = if let Some(struct_id) = constructor.struct_type { @@ -448,10 +466,13 @@ impl<'context> Elaborator<'context> { let generics = typ.borrow().instantiate(self.interner); (typ, generics) } else { - match self.lookup_type_or_error(constructor.type_name) { + match self.lookup_type_or_error(path) { Some(Type::Struct(r#type, struct_generics)) => (r#type, struct_generics), Some(typ) => { - self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); + self.push_err(ResolverError::NonStructUsedInConstructor { + typ: typ.to_string(), + span, + }); return (HirExpression::Error, Type::Error); } None => return (HirExpression::Error, Type::Error), diff --git a/compiler/noirc_frontend/src/elaborator/patterns.rs b/compiler/noirc_frontend/src/elaborator/patterns.rs index 09357e77c0b..c2e42b7574c 100644 --- a/compiler/noirc_frontend/src/elaborator/patterns.rs +++ b/compiler/noirc_frontend/src/elaborator/patterns.rs @@ -188,6 +188,7 @@ impl<'context> Elaborator<'context> { Some(Type::Struct(struct_type, generics)) => (struct_type, generics), None => return error_identifier(self), Some(typ) => { + let typ = typ.to_string(); self.push_err(ResolverError::NonStructUsedInConstructor { typ, span }); return error_identifier(self); } diff --git a/compiler/noirc_frontend/src/hir/comptime/errors.rs b/compiler/noirc_frontend/src/hir/comptime/errors.rs index 9c4761f3156..e97d0ae7d5e 100644 --- a/compiler/noirc_frontend/src/hir/comptime/errors.rs +++ b/compiler/noirc_frontend/src/hir/comptime/errors.rs @@ -394,8 +394,8 @@ impl<'a> From<&'a InterpreterError> for CustomDiagnostic { let mut diagnostic = CustomDiagnostic::simple_error(primary, secondary, location.span); - // Only take at most 3 frames starting from the top of the stack to avoid producing too much output - for frame in call_stack.iter().rev().take(3) { + // Only take at most 5 frames starting from the top of the stack to avoid producing too much output + for frame in call_stack.iter().rev().take(5) { diagnostic.add_secondary_with_file("".to_string(), frame.span, frame.file); } diff --git a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs index c404b95d4b2..d998dfa69fd 100644 --- a/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs +++ b/compiler/noirc_frontend/src/hir/comptime/hir_to_display_ast.rs @@ -141,7 +141,7 @@ impl HirExpression { let struct_type = None; ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name, + typ: UnresolvedType::from_path(type_name), fields, struct_type, })) diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 8ca66112766..3b35b49ead3 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -7,7 +7,8 @@ use builtin_helpers::{ get_format_string, get_function_def, get_module, get_quoted, get_slice, get_struct, get_trait_constraint, get_trait_def, get_trait_impl, get_tuple, get_type, get_typed_expr, get_u32, get_unresolved_type, has_named_attribute, hir_pattern_to_tokens, - mutate_func_meta_type, parse, replace_func_meta_parameters, replace_func_meta_return_type, + mutate_func_meta_type, parse, quote_ident, replace_func_meta_parameters, + replace_func_meta_return_type, }; use chumsky::{chain::Chain, prelude::choice, Parser}; use im::Vector; @@ -22,13 +23,13 @@ use crate::{ FunctionReturnType, IntegerBitSize, LValue, Literal, Pattern, Statement, StatementKind, UnaryOp, UnresolvedType, UnresolvedTypeData, Visibility, }, - hir::def_collector::dc_crate::CollectedItems, hir::{ comptime::{ errors::IResult, value::{ExprValue, TypedExpr}, InterpreterError, Value, }, + def_collector::dc_crate::CollectedItems, def_map::ModuleId, }, hir_def::function::FunctionBody, @@ -71,6 +72,9 @@ impl<'local, 'context> Interpreter<'local, 'context> { "expr_as_bool" => expr_as_bool(interner, arguments, return_type, location), "expr_as_cast" => expr_as_cast(interner, arguments, return_type, location), "expr_as_comptime" => expr_as_comptime(interner, arguments, return_type, location), + "expr_as_constructor" => { + expr_as_constructor(interner, arguments, return_type, location) + } "expr_as_function_call" => { expr_as_function_call(interner, arguments, return_type, location) } @@ -1412,6 +1416,38 @@ fn expr_as_comptime( }) } +// fn as_constructor(self) -> Option<(Quoted, [(Quoted, Expr)])> +fn expr_as_constructor( + interner: &mut NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let expr_value = get_expr(interner, self_argument)?; + let expr_value = unwrap_expr_value(interner, expr_value); + + let option_value = + if let ExprValue::Expression(ExpressionKind::Constructor(constructor)) = expr_value { + let typ = Value::UnresolvedType(constructor.typ.typ); + let fields = constructor.fields.into_iter(); + let fields = fields.map(|(name, value)| { + Value::Tuple(vec![quote_ident(&name), Value::expression(value.kind)]) + }); + let fields = fields.collect(); + let fields_type = Type::Slice(Box::new(Type::Tuple(vec![ + Type::Quoted(QuotedType::Quoted), + Type::Quoted(QuotedType::Expr), + ]))); + let fields = Value::Slice(fields, fields_type); + Some(Value::Tuple(vec![typ, fields])) + } else { + None + }; + + option(return_type, option_value) +} + // fn as_function_call(self) -> Option<(Expr, [Expr])> fn expr_as_function_call( interner: &NodeInterner, @@ -1553,15 +1589,13 @@ fn expr_as_member_access( ) -> IResult { expr_as(interner, arguments, return_type, location, |expr| match expr { ExprValue::Expression(ExpressionKind::MemberAccess(member_access)) => { - let tokens = Rc::new(vec![Token::Ident(member_access.rhs.0.contents.clone())]); Some(Value::Tuple(vec![ Value::expression(member_access.lhs.kind), - Value::Quoted(tokens), + quote_ident(&member_access.rhs), ])) } ExprValue::LValue(crate::ast::LValue::MemberAccess { object, field_name, span: _ }) => { - let tokens = Rc::new(vec![Token::Ident(field_name.0.contents.clone())]); - Some(Value::Tuple(vec![Value::lvalue(*object), Value::Quoted(tokens)])) + Some(Value::Tuple(vec![Value::lvalue(*object), quote_ident(&field_name)])) } _ => None, }) @@ -1578,9 +1612,7 @@ fn expr_as_method_call( if let ExprValue::Expression(ExpressionKind::MethodCall(method_call)) = expr { let object = Value::expression(method_call.object.kind); - let name_tokens = - Rc::new(vec![Token::Ident(method_call.method_name.0.contents.clone())]); - let name = Value::Quoted(name_tokens); + let name = quote_ident(&method_call.method_name); let generics = method_call.generics.unwrap_or_default().into_iter(); let generics = generics.map(|generic| Value::UnresolvedType(generic.typ)).collect(); diff --git a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index 1456238e522..db42d6c4170 100644 --- a/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -6,7 +6,7 @@ use noirc_errors::Location; use crate::{ ast::{ - BlockExpression, ExpressionKind, IntegerBitSize, LValue, Pattern, Signedness, + BlockExpression, ExpressionKind, Ident, IntegerBitSize, LValue, Pattern, Signedness, StatementKind, UnresolvedTypeData, }, hir::{ @@ -468,6 +468,14 @@ pub(super) fn has_named_attribute(name: &str, attributes: &[SecondaryAttribute]) false } +pub(super) fn quote_ident(ident: &Ident) -> Value { + Value::Quoted(ident_to_tokens(ident)) +} + +pub(super) fn ident_to_tokens(ident: &Ident) -> Rc> { + Rc::new(vec![Token::Ident(ident.0.contents.clone())]) +} + pub(super) fn hash_item( arguments: Vec<(Value, Location)>, location: Location, diff --git a/compiler/noirc_frontend/src/hir/comptime/value.rs b/compiler/noirc_frontend/src/hir/comptime/value.rs index d517b6fab38..966138d9dea 100644 --- a/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -244,7 +244,7 @@ impl Value { // Since we've provided the struct_type, the path should be ignored. let type_name = Path::from_single(String::new(), location.span); ExpressionKind::Constructor(Box::new(ConstructorExpression { - type_name, + typ: UnresolvedType::from_path(type_name), fields, struct_type, })) diff --git a/compiler/noirc_frontend/src/hir/resolution/errors.rs b/compiler/noirc_frontend/src/hir/resolution/errors.rs index cb726a01dec..92c71c0ebf8 100644 --- a/compiler/noirc_frontend/src/hir/resolution/errors.rs +++ b/compiler/noirc_frontend/src/hir/resolution/errors.rs @@ -55,7 +55,7 @@ pub enum ResolverError { #[error("Test functions are not allowed to have any parameters")] TestFunctionHasParameters { span: Span }, #[error("Only struct types can be used in constructor expressions")] - NonStructUsedInConstructor { typ: Type, span: Span }, + NonStructUsedInConstructor { typ: String, span: Span }, #[error("Only struct types can have generics")] NonStructWithGenerics { span: Span }, #[error("Cannot apply generics on Self type")] diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index 0ffeb691d35..0c4dbfda99e 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -28,6 +28,7 @@ use self::primitives::{keyword, macro_quote_marker, mutable_reference, variable} use self::types::{generic_type_args, maybe_comp_time}; use attributes::{attributes, inner_attribute, validate_secondary_attributes}; use doc_comments::{inner_doc_comments, outer_doc_comments}; +use types::interned_unresolved_type; pub use types::parse_type; use visibility::item_visibility; pub use visibility::visibility; @@ -1228,7 +1229,11 @@ fn constructor(expr_parser: impl ExprParser) -> impl NoirParser .allow_trailing() .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - path(super::parse_type()).then(args).map(ExpressionKind::constructor) + let path = path(super::parse_type()).map(UnresolvedType::from_path); + let interned_unresolved_type = interned_unresolved_type(); + let typ = choice((path, interned_unresolved_type)); + + typ.then(args).map(ExpressionKind::constructor) } fn constructor_field

(expr_parser: P) -> impl NoirParser<(Ident, Expression)> diff --git a/docs/docs/noir/standard_library/meta/expr.md b/docs/docs/noir/standard_library/meta/expr.md index 7ee33027354..c56568f5379 100644 --- a/docs/docs/noir/standard_library/meta/expr.md +++ b/docs/docs/noir/standard_library/meta/expr.md @@ -52,6 +52,13 @@ a slice containing each statement. If this expression is a boolean literal, return that literal. +### as_cast + +#include_code as_cast noir_stdlib/src/meta/expr.nr rust + +If this expression is a cast expression (`expr as type`), returns the casted +expression and the type to cast to. + ### as_comptime #include_code as_comptime noir_stdlib/src/meta/expr.nr rust @@ -59,6 +66,13 @@ If this expression is a boolean literal, return that literal. If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, return each statement in the block. +### as_constructor + +#include_code as_constructor noir_stdlib/src/meta/expr.nr rust + +If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`, +return the type and the fields. + ### as_function_call #include_code as_function_call noir_stdlib/src/meta/expr.nr rust diff --git a/noir_stdlib/src/meta/expr.nr b/noir_stdlib/src/meta/expr.nr index 72e1a88cea8..5c6a6f2236e 100644 --- a/noir_stdlib/src/meta/expr.nr +++ b/noir_stdlib/src/meta/expr.nr @@ -1,131 +1,209 @@ +//! Contains methods on the built-in `Expr` type for quoted, syntactically valid expressions. + use crate::option::Option; use crate::meta::op::UnaryOp; use crate::meta::op::BinaryOp; impl Expr { + /// If this expression is an array literal `[elem1, ..., elemN]`, this returns a slice of each element in the array. #[builtin(expr_as_array)] // docs:start:as_array comptime fn as_array(self) -> Option<[Expr]> {} // docs:end:as_array + /// If this expression is an assert, this returns the assert expression and the optional message. #[builtin(expr_as_assert)] // docs:start:as_assert comptime fn as_assert(self) -> Option<(Expr, Option)> {} // docs:end:as_assert + /// If this expression is an assert_eq, this returns the left-hand-side and right-hand-side + /// expressions, together with the optional message. #[builtin(expr_as_assert_eq)] // docs:start:as_assert_eq comptime fn as_assert_eq(self) -> Option<(Expr, Expr, Option)> {} // docs:end:as_assert_eq + /// If this expression is an assignment, this returns a tuple with the left hand side + /// and right hand side in order. #[builtin(expr_as_assign)] // docs:start:as_assign comptime fn as_assign(self) -> Option<(Expr, Expr)> {} // docs:end:as_assign - #[builtin(expr_as_integer)] - // docs:start:as_integer - comptime fn as_integer(self) -> Option<(Field, bool)> {} - // docs:end:as_integer - + /// If this expression is a binary operator operation ` `, + /// return the left-hand side, operator, and the right-hand side of the operation. #[builtin(expr_as_binary_op)] // docs:start:as_binary_op comptime fn as_binary_op(self) -> Option<(Expr, BinaryOp, Expr)> {} // docs:end:as_binary_op + /// If this expression is a block `{ stmt1; stmt2; ...; stmtN }`, return + /// a slice containing each statement. #[builtin(expr_as_block)] // docs:start:as_block comptime fn as_block(self) -> Option<[Expr]> {} // docs:end:as_block + /// If this expression is a boolean literal, return that literal. #[builtin(expr_as_bool)] // docs:start:as_bool comptime fn as_bool(self) -> Option {} // docs:end:as_bool + /// If this expression is a cast expression `expr as type`, returns the casted + /// expression and the type to cast to. + // docs:start:as_cast #[builtin(expr_as_cast)] comptime fn as_cast(self) -> Option<(Expr, UnresolvedType)> {} + // docs:end:as_cast + /// If this expression is a `comptime { stmt1; stmt2; ...; stmtN }` block, + /// return each statement in the block. #[builtin(expr_as_comptime)] // docs:start:as_comptime comptime fn as_comptime(self) -> Option<[Expr]> {} // docs:end:as_comptime + /// If this expression is a constructor `Type { field1: expr1, ..., fieldN: exprN }`, + /// return the type and the fields. + #[builtin(expr_as_constructor)] + // docs:start:as_constructor + comptime fn as_constructor(self) -> Option<(UnresolvedType, [(Quoted, Expr)])> {} + // docs:end:as_constructor + + /// If this expression is a function call `foo(arg1, ..., argN)`, return + /// the function and a slice of each argument. #[builtin(expr_as_function_call)] // docs:start:as_function_call comptime fn as_function_call(self) -> Option<(Expr, [Expr])> {} // docs:end:as_function_call + /// If this expression is an `if condition { then_branch } else { else_branch }`, + /// return the condition, then branch, and else branch. If there is no else branch, + /// `None` is returned for that branch instead. #[builtin(expr_as_if)] // docs:start:as_if comptime fn as_if(self) -> Option<(Expr, Expr, Option)> {} // docs:end:as_if + /// If this expression is an index into an array `array[index]`, return the + /// array and the index. #[builtin(expr_as_index)] // docs:start:as_index comptime fn as_index(self) -> Option<(Expr, Expr)> {} // docs:end:as_index + /// If this expression is an integer literal, return the integer as a field + /// as well as whether the integer is negative (true) or not (false). + #[builtin(expr_as_integer)] + // docs:start:as_integer + comptime fn as_integer(self) -> Option<(Field, bool)> {} + // docs:end:as_integer + + /// If this expression is a let statement, returns the let pattern as an `Expr`, + /// the optional type annotation, and the assigned expression. #[builtin(expr_as_let)] // docs:start:as_let comptime fn as_let(self) -> Option<(Expr, Option, Expr)> {} // docs:end:as_let + /// If this expression is a member access `foo.bar`, return the struct/tuple + /// expression and the field. The field will be represented as a quoted value. #[builtin(expr_as_member_access)] // docs:start:as_member_access comptime fn as_member_access(self) -> Option<(Expr, Quoted)> {} // docs:end:as_member_access + /// If this expression is a method call `foo.bar::(arg1, ..., argN)`, return + /// the receiver, method name, a slice of each generic argument, and a slice of each argument. #[builtin(expr_as_method_call)] // docs:start:as_method_call comptime fn as_method_call(self) -> Option<(Expr, Quoted, [UnresolvedType], [Expr])> {} // docs:end:as_method_call + /// If this expression is a repeated element array `[elem; length]`, return + /// the repeated element and the length expressions. #[builtin(expr_as_repeated_element_array)] // docs:start:as_repeated_element_array comptime fn as_repeated_element_array(self) -> Option<(Expr, Expr)> {} // docs:end:as_repeated_element_array + /// If this expression is a repeated element slice `[elem; length]`, return + /// the repeated element and the length expressions. #[builtin(expr_as_repeated_element_slice)] // docs:start:as_repeated_element_slice comptime fn as_repeated_element_slice(self) -> Option<(Expr, Expr)> {} // docs:end:as_repeated_element_slice + /// If this expression is a slice literal `&[elem1, ..., elemN]`, + /// return each element of the slice. #[builtin(expr_as_slice)] // docs:start:as_slice comptime fn as_slice(self) -> Option<[Expr]> {} // docs:end:as_slice + /// If this expression is a tuple `(field1, ..., fieldN)`, + /// return each element of the tuple. #[builtin(expr_as_tuple)] // docs:start:as_tuple comptime fn as_tuple(self) -> Option<[Expr]> {} // docs:end:as_tuple + /// If this expression is a unary operation ` `, + /// return the unary operator as well as the right-hand side expression. #[builtin(expr_as_unary_op)] // docs:start:as_unary_op comptime fn as_unary_op(self) -> Option<(UnaryOp, Expr)> {} // docs:end:as_unary_op + /// If this expression is an `unsafe { stmt1; ...; stmtN }` block, + /// return each statement inside in a slice. #[builtin(expr_as_unsafe)] // docs:start:as_unsafe comptime fn as_unsafe(self) -> Option<[Expr]> {} // docs:end:as_unsafe + /// Returns `true` if this expression is trailed by a semicolon. + /// + /// Example: + /// + /// ```noir + /// comptime { + /// let expr1 = quote { 1 + 2 }.as_expr().unwrap(); + /// let expr2 = quote { 1 + 2; }.as_expr().unwrap(); + /// + /// assert(expr1.as_binary_op().is_some()); + /// assert(expr2.as_binary_op().is_some()); + /// + /// assert(!expr1.has_semicolon()); + /// assert(expr2.has_semicolon()); + /// } + /// ``` #[builtin(expr_has_semicolon)] // docs:start:has_semicolon comptime fn has_semicolon(self) -> bool {} // docs:end:has_semicolon + /// Returns `true` if this expression is `break`. #[builtin(expr_is_break)] // docs:start:is_break comptime fn is_break(self) -> bool {} // docs:end:is_break + /// Returns `true` if this expression is `continue`. #[builtin(expr_is_continue)] // docs:start:is_continue comptime fn is_continue(self) -> bool {} // docs:end:is_continue + /// Applies a mapping function to this expression and to all of its sub-expressions. + /// `f` will be applied to each sub-expression first, then applied to the expression itself. + /// + /// This happens recursively for every expression within `self`. + /// + /// For example, calling `modify` on `(&[1], &[2, 3])` with an `f` that returns `Option::some` + /// for expressions that are integers, doubling them, would return `(&[2], &[4, 6])`. // docs:start:modify comptime fn modify(self, f: fn[Env](Expr) -> Option) -> Expr { // docs:end:modify @@ -137,6 +215,7 @@ impl Expr { let result = result.or_else(|| modify_block(self, f)); let result = result.or_else(|| modify_cast(self, f)); let result = result.or_else(|| modify_comptime(self, f)); + let result = result.or_else(|| modify_constructor(self, f)); let result = result.or_else(|| modify_if(self, f)); let result = result.or_else(|| modify_index(self, f)); let result = result.or_else(|| modify_let(self, f)); @@ -158,12 +237,22 @@ impl Expr { } } + /// Returns this expression as a `Quoted` value. It's the same as `quote { $self }`. // docs:start:quoted comptime fn quoted(self) -> Quoted { // docs:end:quoted quote { $self } } + /// Resolves and type-checks this expression and returns the result as a `TypedExpr`. + /// + /// The `in_function` argument specifies where the expression is resolved: + /// - If it's `none`, the expression is resolved in the function where `resolve` was called + /// - If it's `some`, the expression is resolved in the given function + /// + /// If any names used by this expression are not in scope or if there are any type errors, + /// this will give compiler errors as if the expression was written directly into + /// the current `comptime` function. #[builtin(expr_resolve)] // docs:start:resolve comptime fn resolve(self, in_function: Option) -> TypedExpr {} @@ -252,6 +341,19 @@ comptime fn modify_comptime(expr: Expr, f: fn[Env](Expr) -> Option) - ) } +comptime fn modify_constructor(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { + expr.as_constructor().map( + |expr: (UnresolvedType, [(Quoted, Expr)])| { + let (typ, fields) = expr; + let fields = fields.map(|field: (Quoted, Expr)| { + let (name, value) = field; + (name, value.modify(f)) + }); + new_constructor(typ, fields) + } + ) +} + comptime fn modify_function_call(expr: Expr, f: fn[Env](Expr) -> Option) -> Option { expr.as_function_call().map( |expr: (Expr, [Expr])| { @@ -427,6 +529,16 @@ comptime fn new_comptime(exprs: [Expr]) -> Expr { quote { comptime { $exprs }}.as_expr().unwrap() } +comptime fn new_constructor(typ: UnresolvedType, fields: [(Quoted, Expr)]) -> Expr { + let fields = fields.map( + |field: (Quoted, Expr)| { + let (name, value) = field; + quote { $name: $value } + } + ).join(quote { , }); + quote { $typ { $fields }}.as_expr().unwrap() +} + comptime fn new_if(condition: Expr, consequence: Expr, alternative: Option) -> Expr { if alternative.is_some() { let alternative = alternative.unwrap(); @@ -501,189 +613,3 @@ comptime fn new_unsafe(exprs: [Expr]) -> Expr { comptime fn join_expressions(exprs: [Expr], separator: Quoted) -> Quoted { exprs.map(|expr: Expr| expr.quoted()).join(separator) } - -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_less_than()); - assert(get_binary_op(quote { x <= y }).is_less_than_or_equal()); - assert(get_binary_op(quote { x > y }).is_greater_than()); - assert(get_binary_op(quote { x >= y }).is_greater_than_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/noir_test_success/comptime_expr/src/main.nr b/test_programs/noir_test_success/comptime_expr/src/main.nr index c1f70e7acee..9eb4dc8a694 100644 --- a/test_programs/noir_test_success/comptime_expr/src/main.nr +++ b/test_programs/noir_test_success/comptime_expr/src/main.nr @@ -326,6 +326,35 @@ mod tests { } } + #[test] + fn test_expr_as_constructor() { + comptime + { + let expr = quote { Foo { a: 1, b: 2 } }.as_expr().unwrap(); + let (_typ, fields) = expr.as_constructor().unwrap(); + assert_eq(fields.len(), 2); + assert_eq(fields[0].0, quote { a }); + assert_eq(fields[0].1.as_integer().unwrap(), (1, false)); + assert_eq(fields[1].0, quote { b }); + assert_eq(fields[1].1.as_integer().unwrap(), (2, false)); + } + } + + #[test] + fn test_expr_modify_for_constructor() { + comptime + { + let expr = quote { foo::bar::Baz:: { a: 1, b: 2 } }.as_expr().unwrap(); + let expr = expr.modify(times_two); + let (_typ, fields) = expr.as_constructor().unwrap(); + assert_eq(fields.len(), 2); + assert_eq(fields[0].0, quote { a }); + assert_eq(fields[0].1.as_integer().unwrap(), (2, false)); + assert_eq(fields[1].0, quote { b }); + assert_eq(fields[1].1.as_integer().unwrap(), (4, false)); + } + } + // This test can't only be around the comptime block since that will cause // `nargo fmt` to remove the comptime keyword. // docs:start:as_expr_example diff --git a/tooling/lsp/src/requests/code_action/fill_struct_fields.rs b/tooling/lsp/src/requests/code_action/fill_struct_fields.rs index f57fbc652ad..be8602d99a9 100644 --- a/tooling/lsp/src/requests/code_action/fill_struct_fields.rs +++ b/tooling/lsp/src/requests/code_action/fill_struct_fields.rs @@ -1,6 +1,9 @@ use lsp_types::TextEdit; use noirc_errors::{Location, Span}; -use noirc_frontend::{ast::ConstructorExpression, node_interner::ReferenceId}; +use noirc_frontend::{ + ast::{ConstructorExpression, UnresolvedTypeData}, + node_interner::ReferenceId, +}; use crate::byte_span_to_range; @@ -12,8 +15,11 @@ impl<'a> CodeActionFinder<'a> { return; } - // Find out which struct this is - let location = Location::new(constructor.type_name.last_ident().span(), self.file); + let UnresolvedTypeData::Named(path, _, _) = &constructor.typ.typ else { + return; + }; + + let location = Location::new(path.span, self.file); let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { return; }; diff --git a/tooling/lsp/src/requests/completion.rs b/tooling/lsp/src/requests/completion.rs index 18e406860ab..96eb8b2320a 100644 --- a/tooling/lsp/src/requests/completion.rs +++ b/tooling/lsp/src/requests/completion.rs @@ -20,7 +20,7 @@ use noirc_frontend::{ ItemVisibility, Lambda, LetStatement, MemberAccessExpression, MethodCallExpression, NoirFunction, NoirStruct, NoirTraitImpl, Path, PathKind, Pattern, Statement, TraitImplItemKind, TypeImpl, UnresolvedGeneric, UnresolvedGenerics, UnresolvedType, - UseTree, UseTreeKind, Visitor, + UnresolvedTypeData, UseTree, UseTreeKind, Visitor, }, graph::{CrateId, Dependency}, hir::def_map::{CrateDefMap, LocalModuleId, ModuleId}, @@ -179,8 +179,13 @@ impl<'a> NodeFinder<'a> { } fn complete_constructor_field_name(&mut self, constructor_expression: &ConstructorExpression) { - let location = - Location::new(constructor_expression.type_name.last_ident().span(), self.file); + let span = if let UnresolvedTypeData::Named(path, _, _) = &constructor_expression.typ.typ { + path.last_ident().span() + } else { + constructor_expression.typ.span + }; + + let location = Location::new(span, self.file); let Some(ReferenceId::Struct(struct_id)) = self.interner.find_referenced(location) else { return; }; @@ -1325,7 +1330,11 @@ impl<'a> Visitor for NodeFinder<'a> { constructor_expression: &ConstructorExpression, _: Span, ) -> bool { - self.find_in_path(&constructor_expression.type_name, RequestedItems::OnlyTypes); + let UnresolvedTypeData::Named(path, _, _) = &constructor_expression.typ.typ else { + return true; + }; + + self.find_in_path(path, RequestedItems::OnlyTypes); // Check if we need to autocomplete the field name if constructor_expression diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index caa60b17cc2..2d967e94833 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -143,9 +143,9 @@ pub(crate) fn rewrite( super::parenthesized(visitor, shape, span, *sub_expr) } ExpressionKind::Constructor(constructor) => { - let type_name = visitor.slice(span.start()..constructor.type_name.span().end()); - let fields_span = visitor - .span_before(constructor.type_name.span().end()..span.end(), Token::LeftBrace); + let type_name = visitor.slice(span.start()..constructor.typ.span.end()); + let fields_span = + visitor.span_before(constructor.typ.span.end()..span.end(), Token::LeftBrace); visitor.format_struct_lit(type_name, fields_span, *constructor) }