From 73607260b0259f7c84f02fec7cfad1ad9c2660ac Mon Sep 17 00:00:00 2001 From: jfecher Date: Wed, 20 Mar 2024 10:48:06 -0500 Subject: [PATCH] feat: Add experimental `quote` expression to parser (#4595) # Description ## Problem\* Resolves https://github.com/noir-lang/noir/issues/4586 Part of https://github.com/noir-lang/noir/issues/4594 ## Summary\* Adds a `quote { ... }` expression to the parser along with a new builtin `Code` type which it is the only method of creating. ## Additional Context The quote expression can only currently quote expressions and statements. It cannot yet quote top-level statements - we'd need more parser changes for this. In particular the top level statement parser would now need to be recursive and passed down all the way to the expression level... Trying to use `quote` in a program gives you an `experimental feature` warning. Indeed, the only thing you can do with it currently is panic once it gets to monomorphization without being removed from the runtime program yet. ## Documentation\* Check one: - [x] No documentation needed. - I'm following the pattern with other experimental features and waiting to document them until they're stable. For this, it means quote won't be documented until the base for metaprogramming is completed. - [ ] Documentation included in this PR. - [ ] **[Exceptional Case]** 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. --- compiler/noirc_frontend/src/ast/expression.rs | 2 ++ .../src/hir/resolution/resolver.rs | 4 ++++ .../noirc_frontend/src/hir/type_check/expr.rs | 1 + compiler/noirc_frontend/src/hir_def/expr.rs | 1 + compiler/noirc_frontend/src/hir_def/types.rs | 22 ++++++++++++++++--- compiler/noirc_frontend/src/lexer/token.rs | 3 +++ .../src/monomorphization/mod.rs | 2 ++ compiler/noirc_frontend/src/node_interner.rs | 2 ++ compiler/noirc_frontend/src/parser/parser.rs | 16 +++++++++++++- tooling/nargo_fmt/src/rewrite/expr.rs | 18 ++++++++++----- tooling/noirc_abi/src/lib.rs | 3 ++- 11 files changed, 63 insertions(+), 11 deletions(-) diff --git a/compiler/noirc_frontend/src/ast/expression.rs b/compiler/noirc_frontend/src/ast/expression.rs index 09c09daf9b9..d646a6ca98a 100644 --- a/compiler/noirc_frontend/src/ast/expression.rs +++ b/compiler/noirc_frontend/src/ast/expression.rs @@ -27,6 +27,7 @@ pub enum ExpressionKind { Tuple(Vec), Lambda(Box), Parenthesized(Box), + Quote(BlockExpression), Error, } @@ -495,6 +496,7 @@ impl Display for ExpressionKind { } Lambda(lambda) => lambda.fmt(f), Parenthesized(sub_expr) => write!(f, "({sub_expr})"), + Quote(block) => write!(f, "quote {block}"), Error => write!(f, "Error"), } } diff --git a/compiler/noirc_frontend/src/hir/resolution/resolver.rs b/compiler/noirc_frontend/src/hir/resolution/resolver.rs index c6606386f7b..3fbde8a890b 100644 --- a/compiler/noirc_frontend/src/hir/resolution/resolver.rs +++ b/compiler/noirc_frontend/src/hir/resolution/resolver.rs @@ -1085,6 +1085,7 @@ impl<'a> Resolver<'a> { | Type::Constant(_) | Type::NamedGeneric(_, _) | Type::TraitAsType(..) + | Type::Code | Type::Forall(_, _) => (), Type::Array(length, element_type) => { @@ -1620,6 +1621,9 @@ impl<'a> Resolver<'a> { }) }), ExpressionKind::Parenthesized(sub_expr) => return self.resolve_expression(*sub_expr), + + // The quoted expression isn't resolved since we don't want errors if variables aren't defined + ExpressionKind::Quote(block) => HirExpression::Quote(block), }; // If these lines are ever changed, make sure to change the early return diff --git a/compiler/noirc_frontend/src/hir/type_check/expr.rs b/compiler/noirc_frontend/src/hir/type_check/expr.rs index eb998b59755..6ee0460784f 100644 --- a/compiler/noirc_frontend/src/hir/type_check/expr.rs +++ b/compiler/noirc_frontend/src/hir/type_check/expr.rs @@ -332,6 +332,7 @@ impl<'interner> TypeChecker<'interner> { Type::Function(params, Box::new(lambda.return_type), Box::new(env_type)) } + HirExpression::Quote(_) => Type::Code, }; self.interner.push_expr_type(*expr_id, typ.clone()); diff --git a/compiler/noirc_frontend/src/hir_def/expr.rs b/compiler/noirc_frontend/src/hir_def/expr.rs index 6a1b4385f0d..61743d2cdc7 100644 --- a/compiler/noirc_frontend/src/hir_def/expr.rs +++ b/compiler/noirc_frontend/src/hir_def/expr.rs @@ -31,6 +31,7 @@ pub enum HirExpression { Tuple(Vec), Lambda(HirLambda), Error, + Quote(crate::BlockExpression), } impl HirExpression { diff --git a/compiler/noirc_frontend/src/hir_def/types.rs b/compiler/noirc_frontend/src/hir_def/types.rs index 7fb26aa3879..3c5627f739b 100644 --- a/compiler/noirc_frontend/src/hir_def/types.rs +++ b/compiler/noirc_frontend/src/hir_def/types.rs @@ -101,6 +101,9 @@ pub enum Type { /// bind to an integer without special checks to bind it to a non-type. Constant(u64), + /// The type of quoted code in macros. This is always a comptime-only type + Code, + /// 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 @@ -144,6 +147,7 @@ impl Type { | Type::MutableReference(_) | Type::Forall(_, _) | Type::Constant(_) + | Type::Code | Type::Slice(_) | Type::Error => unreachable!("This type cannot exist as a parameter to main"), } @@ -626,6 +630,7 @@ impl Type { | Type::Constant(_) | Type::NamedGeneric(_, _) | Type::Forall(_, _) + | Type::Code | Type::TraitAsType(..) => false, Type::Array(length, elem) => { @@ -689,6 +694,7 @@ impl Type { | Type::Function(_, _, _) | Type::MutableReference(_) | Type::Forall(_, _) + | Type::Code | Type::Slice(_) | Type::TraitAsType(..) => false, @@ -852,6 +858,7 @@ impl std::fmt::Display for Type { Type::MutableReference(element) => { write!(f, "&mut {element}") } + Type::Code => write!(f, "Code"), } } } @@ -1529,6 +1536,7 @@ impl Type { | Type::Constant(_) | Type::TraitAsType(..) | Type::Error + | Type::Code | Type::Unit => self.clone(), } } @@ -1570,6 +1578,7 @@ impl Type { | Type::Constant(_) | Type::TraitAsType(..) | Type::Error + | Type::Code | Type::Unit => false, } } @@ -1621,9 +1630,14 @@ impl Type { // Expect that this function should only be called on instantiated types Forall(..) => unreachable!(), - TraitAsType(..) | FieldElement | Integer(_, _) | Bool | Constant(_) | Unit | Error => { - self.clone() - } + TraitAsType(..) + | FieldElement + | Integer(_, _) + | Bool + | Constant(_) + | Unit + | Code + | Error => self.clone(), } } @@ -1752,6 +1766,7 @@ impl From<&Type> for PrintableType { Type::MutableReference(typ) => { PrintableType::MutableReference { typ: Box::new(typ.as_ref().into()) } } + Type::Code => unreachable!(), } } } @@ -1836,6 +1851,7 @@ impl std::fmt::Debug for Type { Type::MutableReference(element) => { write!(f, "&mut {element:?}") } + Type::Code => write!(f, "Code"), } } } diff --git a/compiler/noirc_frontend/src/lexer/token.rs b/compiler/noirc_frontend/src/lexer/token.rs index 1e341d34510..4432a3f9e07 100644 --- a/compiler/noirc_frontend/src/lexer/token.rs +++ b/compiler/noirc_frontend/src/lexer/token.rs @@ -667,6 +667,7 @@ pub enum Keyword { Mod, Mut, Pub, + Quote, Return, ReturnData, String, @@ -710,6 +711,7 @@ impl fmt::Display for Keyword { Keyword::Mod => write!(f, "mod"), Keyword::Mut => write!(f, "mut"), Keyword::Pub => write!(f, "pub"), + Keyword::Quote => write!(f, "quote"), Keyword::Return => write!(f, "return"), Keyword::ReturnData => write!(f, "return_data"), Keyword::String => write!(f, "str"), @@ -756,6 +758,7 @@ impl Keyword { "mod" => Keyword::Mod, "mut" => Keyword::Mut, "pub" => Keyword::Pub, + "quote" => Keyword::Quote, "return" => Keyword::Return, "return_data" => Keyword::ReturnData, "str" => Keyword::String, diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index d4fe2e0a2aa..9b0f57a7d39 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -516,6 +516,7 @@ impl<'interner> Monomorphizer<'interner> { unreachable!("Encountered HirExpression::MethodCall during monomorphization {hir_method_call:?}") } HirExpression::Error => unreachable!("Encountered Error node during monomorphization"), + HirExpression::Quote(_) => unreachable!("quote expression remaining in runtime code"), }; Ok(expr) @@ -954,6 +955,7 @@ impl<'interner> Monomorphizer<'interner> { HirType::Forall(_, _) | HirType::Constant(_) | HirType::Error => { unreachable!("Unexpected type {} found", typ) } + HirType::Code => unreachable!("Tried to translate Code type into runtime code"), } } diff --git a/compiler/noirc_frontend/src/node_interner.rs b/compiler/noirc_frontend/src/node_interner.rs index 4e184e1391b..09e0cb04d26 100644 --- a/compiler/noirc_frontend/src/node_interner.rs +++ b/compiler/noirc_frontend/src/node_interner.rs @@ -1672,6 +1672,7 @@ enum TypeMethodKey { Tuple, Function, Generic, + Code, } fn get_type_method_key(typ: &Type) -> Option { @@ -1691,6 +1692,7 @@ fn get_type_method_key(typ: &Type) -> Option { Type::Tuple(_) => Some(Tuple), Type::Function(_, _, _) => Some(Function), Type::NamedGeneric(_, _) => Some(Generic), + Type::Code => Some(Code), Type::MutableReference(element) => get_type_method_key(element), Type::Alias(alias, _) => get_type_method_key(&alias.borrow().typ), diff --git a/compiler/noirc_frontend/src/parser/parser.rs b/compiler/noirc_frontend/src/parser/parser.rs index f2e22da3c2c..dec1c7aa9ce 100644 --- a/compiler/noirc_frontend/src/parser/parser.rs +++ b/compiler/noirc_frontend/src/parser/parser.rs @@ -1150,7 +1150,8 @@ where nothing().boxed() }, lambdas::lambda(expr_parser.clone()), - block(statement).map(ExpressionKind::Block), + block(statement.clone()).map(ExpressionKind::Block), + quote(statement), variable(), literal(), )) @@ -1175,6 +1176,19 @@ where .labelled(ParsingRuleLabel::Atom) } +fn quote<'a, P>(statement: P) -> impl NoirParser + 'a +where + P: NoirParser + 'a, +{ + keyword(Keyword::Quote).ignore_then(block(statement)).validate(|block, span, emit| { + emit(ParserError::with_reason( + ParserErrorReason::ExperimentalFeature("quoted expressions"), + span, + )); + ExpressionKind::Quote(block) + }) +} + fn tuple

(expr_parser: P) -> impl NoirParser where P: ExprParser, diff --git a/tooling/nargo_fmt/src/rewrite/expr.rs b/tooling/nargo_fmt/src/rewrite/expr.rs index ffb8dea5a9d..6cf69a2309d 100644 --- a/tooling/nargo_fmt/src/rewrite/expr.rs +++ b/tooling/nargo_fmt/src/rewrite/expr.rs @@ -1,4 +1,7 @@ -use noirc_frontend::{token::Token, ArrayLiteral, Expression, ExpressionKind, Literal, UnaryOp}; +use noirc_frontend::{ + macros_api::Span, token::Token, ArrayLiteral, BlockExpression, Expression, ExpressionKind, + Literal, UnaryOp, +}; use crate::visitor::{ expr::{format_brackets, format_parens, NewlineMode}, @@ -20,11 +23,7 @@ pub(crate) fn rewrite( shape: Shape, ) -> String { match kind { - ExpressionKind::Block(block) => { - let mut visitor = visitor.fork(); - visitor.visit_block(block, span); - visitor.finish() - } + ExpressionKind::Block(block) => rewrite_block(visitor, block, span), ExpressionKind::Prefix(prefix) => { let op = match prefix.operator { UnaryOp::Minus => "-", @@ -159,6 +158,13 @@ pub(crate) fn rewrite( visitor.format_if(*if_expr) } ExpressionKind::Lambda(_) | ExpressionKind::Variable(_) => visitor.slice(span).to_string(), + ExpressionKind::Quote(block) => format!("quote {}", rewrite_block(visitor, block, span)), ExpressionKind::Error => unreachable!(), } } + +fn rewrite_block(visitor: &FmtVisitor, block: BlockExpression, span: Span) -> String { + let mut visitor = visitor.fork(); + visitor.visit_block(block, span); + visitor.finish() +} diff --git a/tooling/noirc_abi/src/lib.rs b/tooling/noirc_abi/src/lib.rs index 26edb7a2af6..cffb0c20cd5 100644 --- a/tooling/noirc_abi/src/lib.rs +++ b/tooling/noirc_abi/src/lib.rs @@ -178,8 +178,9 @@ impl AbiType { | Type::TypeVariable(_, _) | Type::NamedGeneric(..) | Type::Forall(..) + | Type::Code | Type::Slice(_) - | Type::Function(_, _, _) => unreachable!("Type cannot be used in the abi"), + | Type::Function(_, _, _) => unreachable!("{typ} cannot be used in the abi"), Type::FmtString(_, _) => unreachable!("format strings cannot be used in the abi"), Type::MutableReference(_) => unreachable!("&mut cannot be used in the abi"), }