diff --git a/.noir-sync-commit b/.noir-sync-commit index 256571eae0c..19e6077a33b 100644 --- a/.noir-sync-commit +++ b/.noir-sync-commit @@ -1 +1 @@ -c3cb38a7c4de6fc321b367eda3fca6d06e76b77a +c4273a0c8f8b751a3dbe097e070e4e7b2c8ec438 diff --git a/avm-transpiler/Cargo.lock b/avm-transpiler/Cargo.lock index 9fca01a53c7..775cd44d256 100644 --- a/avm-transpiler/Cargo.lock +++ b/avm-transpiler/Cargo.lock @@ -65,17 +65,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.11" @@ -436,14 +425,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "chumsky" -version = "0.8.0" -source = "git+https://github.com/jfecher/chumsky?rev=ad9d312#ad9d312d9ffbc66c14514fa2b5752f4127b44f1e" -dependencies = [ - "hashbrown 0.11.2", -] - [[package]] name = "codespan" version = "0.11.1" @@ -749,15 +730,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -770,7 +742,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.11", + "ahash", ] [[package]] @@ -968,7 +940,6 @@ version = "0.35.0" dependencies = [ "acvm", "base64 0.21.7", - "chumsky", "codespan", "codespan-reporting", "flate2", diff --git a/noir/noir-repo/Cargo.lock b/noir/noir-repo/Cargo.lock index a5544310617..0c0d4afc579 100644 --- a/noir/noir-repo/Cargo.lock +++ b/noir/noir-repo/Cargo.lock @@ -131,17 +131,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.15", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.11" @@ -761,14 +750,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "chumsky" -version = "0.8.0" -source = "git+https://github.com/jfecher/chumsky?rev=ad9d312#ad9d312d9ffbc66c14514fa2b5752f4127b44f1e" -dependencies = [ - "hashbrown 0.11.2", -] - [[package]] name = "ciborium" version = "0.2.1" @@ -1830,15 +1811,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -1851,7 +1823,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash 0.8.11", + "ahash", ] [[package]] @@ -2099,7 +2071,7 @@ version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" dependencies = [ - "ahash 0.8.11", + "ahash", "clap", "crossbeam-channel", "crossbeam-utils", @@ -2597,9 +2569,12 @@ dependencies = [ "pprof 0.13.0", "predicates 2.1.5", "prettytable-rs", + "proptest", "rayon", "serde", "serde_json", + "sha2", + "sha3", "similar-asserts", "tempfile", "termcolor", @@ -2754,7 +2729,6 @@ version = "0.35.0" dependencies = [ "acvm", "async-lsp", - "chumsky", "codespan-lsp", "convert_case 0.6.0", "fm", @@ -2904,7 +2878,6 @@ version = "0.35.0" dependencies = [ "acvm", "base64 0.21.7", - "chumsky", "codespan", "codespan-reporting", "flate2", @@ -2947,7 +2920,6 @@ dependencies = [ "base64 0.21.7", "bn254_blackbox_solver", "cfg-if 1.0.0", - "chumsky", "fm", "im", "iter-extended", diff --git a/noir/noir-repo/Cargo.toml b/noir/noir-repo/Cargo.toml index 6741fb8a98e..0a282631288 100644 --- a/noir/noir-repo/Cargo.toml +++ b/noir/noir-repo/Cargo.toml @@ -87,8 +87,12 @@ bb_abstraction_leaks = { path = "tooling/bb_abstraction_leaks" } acvm_cli = { path = "tooling/acvm_cli" } # Arkworks -ark-bn254 = { version = "^0.4.0", default-features = false, features = ["curve"] } -ark-bls12-381 = { version = "^0.4.0", default-features = false, features = ["curve"] } +ark-bn254 = { version = "^0.4.0", default-features = false, features = [ + "curve", +] } +ark-bls12-381 = { version = "^0.4.0", default-features = false, features = [ + "curve", +] } grumpkin = { version = "0.1.0", package = "noir_grumpkin", features = ["std"] } ark-ec = { version = "^0.4.0", default-features = false } ark-ff = { version = "^0.4.0", default-features = false } @@ -117,10 +121,6 @@ clap = { version = "4.3.19", features = ["derive", "env"] } codespan = { version = "0.11.1", features = ["serialization"] } codespan-lsp = "0.11.1" codespan-reporting = "0.11.1" -chumsky = { git = "https://github.com/jfecher/chumsky", rev = "ad9d312", default-features = false, features = [ - "ahash", - "std", -] } # Benchmarking criterion = "0.5.0" @@ -153,6 +153,8 @@ rand = "0.8.5" proptest = "1.2.0" proptest-derive = "0.4.0" rayon = "1.8.0" +sha2 = { version = "0.10.6", features = ["compress"] } +sha3 = "0.10.6" im = { version = "15.1", features = ["serde"] } tracing = "0.1.40" diff --git a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml index 51c782d9581..b57c9356198 100644 --- a/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml +++ b/noir/noir-repo/acvm-repo/blackbox_solver/Cargo.toml @@ -22,8 +22,8 @@ num-bigint = "0.4" blake2 = "0.10.6" blake3 = "1.5.0" -sha2 = { version="0.10.6", features = ["compress",] } -sha3 = "0.10.6" +sha2.workspace = true +sha3.workspace = true keccak = "0.1.4" k256 = { version = "0.11.0", features = [ "ecdsa", diff --git a/noir/noir-repo/compiler/noirc_errors/Cargo.toml b/noir/noir-repo/compiler/noirc_errors/Cargo.toml index 61b274c605f..a6927eb647a 100644 --- a/noir/noir-repo/compiler/noirc_errors/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_errors/Cargo.toml @@ -16,7 +16,6 @@ acvm.workspace = true codespan-reporting.workspace = true codespan.workspace = true fm.workspace = true -chumsky.workspace = true noirc_printable_type.workspace = true serde.workspace = true serde_with = "3.2.0" diff --git a/noir/noir-repo/compiler/noirc_errors/src/position.rs b/noir/noir-repo/compiler/noirc_errors/src/position.rs index 9b031f56ae2..8131db323b9 100644 --- a/noir/noir-repo/compiler/noirc_errors/src/position.rs +++ b/noir/noir-repo/compiler/noirc_errors/src/position.rs @@ -8,7 +8,7 @@ use std::{ pub type Position = u32; -#[derive(PartialOrd, Eq, Ord, Debug, Clone)] +#[derive(PartialOrd, Eq, Ord, Debug, Clone, Default)] pub struct Spanned { pub contents: T, span: Span, @@ -121,26 +121,6 @@ impl From> for Span { } } -impl chumsky::Span for Span { - type Context = (); - - type Offset = u32; - - fn new(_context: Self::Context, range: Range) -> Self { - Span(ByteSpan::from(range)) - } - - fn context(&self) -> Self::Context {} - - fn start(&self) -> Self::Offset { - self.start() - } - - fn end(&self) -> Self::Offset { - self.end() - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct Location { pub span: Span, diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs index 15d72c2ae74..b560fafd337 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/acir_gen/mod.rs @@ -1303,6 +1303,29 @@ impl<'a> Context<'a> { } } + /// Returns the acir value at the provided databus offset + fn get_from_call_data( + &mut self, + offset: &mut AcirVar, + call_data_block: BlockId, + typ: &Type, + ) -> Result { + match typ { + Type::Numeric(_) => self.array_get_value(&Type::field(), call_data_block, offset), + Type::Array(arc, len) => { + let mut result = Vector::new(); + for _i in 0..*len { + for sub_type in arc.iter() { + let element = self.get_from_call_data(offset, call_data_block, sub_type)?; + result.push_back(element); + } + } + Ok(AcirValue::Array(result)) + } + _ => unimplemented!("Unsupported type in databus"), + } + } + /// Generates a read opcode for the array /// `index_side_effect == false` means that we ensured `var_index` will have a type matching the value in the array fn array_get( @@ -1316,27 +1339,19 @@ impl<'a> Context<'a> { let block_id = self.ensure_array_is_initialized(array, dfg)?; let results = dfg.instruction_results(instruction); let res_typ = dfg.type_of_value(results[0]); - // Get operations to call-data parameters are replaced by a get to the call-data-bus array - if let Some(call_data) = - self.data_bus.call_data.iter().find(|cd| cd.index_map.contains_key(&array)) - { - let type_size = res_typ.flattened_size(); - let type_size = self.acir_context.add_constant(FieldElement::from(type_size as i128)); - let offset = self.acir_context.mul_var(var_index, type_size)?; + let call_data = + self.data_bus.call_data.iter().find(|cd| cd.index_map.contains_key(&array)).cloned(); + if let Some(call_data) = call_data { + let call_data_block = self.ensure_array_is_initialized(call_data.array_id, dfg)?; let bus_index = self .acir_context .add_constant(FieldElement::from(call_data.index_map[&array] as i128)); - let new_index = self.acir_context.add_var(offset, bus_index)?; - return self.array_get( - instruction, - call_data.array_id, - new_index, - dfg, - index_side_effect, - ); + let mut current_index = self.acir_context.add_var(bus_index, var_index)?; + let result = self.get_from_call_data(&mut current_index, call_data_block, &res_typ)?; + self.define_result(dfg, instruction, result.clone()); + return Ok(result); } - // Compiler sanity check assert!( !res_typ.contains_slice_element(), diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs index 3056fb5973d..de9ae8a24d7 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/function_builder/data_bus.rs @@ -121,16 +121,24 @@ impl FunctionBuilder { databus.index += 1; } Type::Array(typ, len) => { - assert!(typ.len() == 1, "unsupported composite type"); databus.map.insert(value, databus.index); - for i in 0..len { - // load each element of the array - let index = self - .current_function - .dfg - .make_constant(FieldElement::from(i as i128), Type::length_type()); - let element = self.insert_array_get(value, index, typ[0].clone()); - self.add_to_data_bus(element, databus); + + let mut index = 0; + for _i in 0..len { + for subitem_typ in typ.iter() { + // load each element of the array, and add it to the databus + let index_var = self + .current_function + .dfg + .make_constant(FieldElement::from(index as i128), Type::length_type()); + let element = self.insert_array_get(value, index_var, subitem_typ.clone()); + index += match subitem_typ { + Type::Array(_, _) | Type::Slice(_) => subitem_typ.element_size(), + Type::Numeric(_) => 1, + _ => unreachable!("Unsupported type for databus"), + }; + self.add_to_data_bus(element, databus); + } } } Type::Reference(_) => { diff --git a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs index 676bb48c4d9..6aa9acaca22 100644 --- a/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs +++ b/noir/noir-repo/compiler/noirc_evaluator/src/ssa/ir/instruction.rs @@ -818,25 +818,96 @@ fn try_optimize_array_get_from_previous_set( SimplifyResult::None } +/// If we have an array set whose value is from an array get on the same array at the same index, +/// we can simplify that array set to the array we were looking to perform an array set upon. +/// +/// Simple case: +/// v3 = array_get v1, index v2 +/// v5 = array_set v1, index v2, value v3 +/// +/// If we could not immediately simplify the array set from its value, we can try to follow +/// the array set backwards in the case we have constant indices: +/// +/// v3 = array_get v1, index 1 +/// v5 = array_set v1, index 2, value [Field 100, Field 101, Field 102] +/// v7 = array_set mut v5, index 1, value v3 +/// +/// We want to optimize `v7` to `v5`. We see that `v3` comes from an array get to `v1`. We follow `v5` backwards and see an array set +/// to `v1` and see that the previous array set occurs to a different constant index. +/// +/// For each array_set: +/// - If the index is non-constant we fail the optimization since any index may be changed. +/// - If the index is constant and is our target index, we conservatively fail the optimization. +/// - Otherwise, we check the array value of the `array_set`. We will refer to this array as array'. +/// In the case above, array' is `v1` from `v5 = array set ...` +/// - If the original `array_set` value comes from an `array_get`, check the array in that `array_get` against array'. +/// - If the two values are equal we can simplify. +/// - Continuing the example above, as we have `v3 = array_get v1, index 1`, `v1` is +/// what we want to check against array'. We now know we can simplify `v7` to `v5` as it is unchanged. +/// - If they are not equal, recur marking the current `array_set` array as the new array id to use in the checks fn try_optimize_array_set_from_previous_get( dfg: &DataFlowGraph, - array_id: ValueId, + mut array_id: ValueId, target_index: ValueId, target_value: ValueId, ) -> SimplifyResult { - match &dfg[target_value] { + let array_from_get = match &dfg[target_value] { Value::Instruction { instruction, .. } => match &dfg[*instruction] { Instruction::ArrayGet { array, index } => { if *array == array_id && *index == target_index { - SimplifyResult::SimplifiedTo(array_id) + // If array and index match from the value, we can immediately simplify + return SimplifyResult::SimplifiedTo(array_id); + } else if *index == target_index { + *array } else { - SimplifyResult::None + return SimplifyResult::None; } } - _ => SimplifyResult::None, + _ => return SimplifyResult::None, }, - _ => SimplifyResult::None, + _ => return SimplifyResult::None, + }; + + // At this point we have determined that the value we are writing in the `array_set` instruction + // comes from an `array_get` from the same index at which we want to write it at. + // It's possible that we're acting on the same array where other indices have been mutated in between + // the `array_get` and `array_set` (resulting in the `array_id` not matching). + // + // We then inspect the set of `array_set`s which which led to the current array the `array_set` is acting on. + // If we can work back to the array on which the `array_get` was reading from without having another `array_set` + // act on the same index then we can be sure that the new `array_set` can be removed without affecting the final result. + let Some(target_index) = dfg.get_numeric_constant(target_index) else { + return SimplifyResult::None; + }; + + let original_array_id = array_id; + // Arbitrary number of maximum tries just to prevent this optimization from taking too long. + let max_tries = 5; + for _ in 0..max_tries { + match &dfg[array_id] { + Value::Instruction { instruction, .. } => match &dfg[*instruction] { + Instruction::ArraySet { array, index, .. } => { + let Some(index) = dfg.get_numeric_constant(*index) else { + return SimplifyResult::None; + }; + + if index == target_index { + return SimplifyResult::None; + } + + if *array == array_from_get { + return SimplifyResult::SimplifiedTo(original_array_id); + } + + array_id = *array; // recur + } + _ => return SimplifyResult::None, + }, + _ => return SimplifyResult::None, + } } + + SimplifyResult::None } pub(crate) type ErrorType = HirType; diff --git a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml index 510cff08dec..d729dabcb04 100644 --- a/noir/noir-repo/compiler/noirc_frontend/Cargo.toml +++ b/noir/noir-repo/compiler/noirc_frontend/Cargo.toml @@ -16,7 +16,6 @@ noirc_errors.workspace = true noirc_printable_type.workspace = true fm.workspace = true iter-extended.workspace = true -chumsky.workspace = true thiserror.workspace = true smol_str.workspace = true im.workspace = true diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs index 362f94171d3..64edae8322f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/expression.rs @@ -309,6 +309,7 @@ impl Expression { pub type BinaryOp = Spanned; #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] +#[cfg_attr(test, derive(strum_macros::EnumIter))] pub enum BinaryOpKind { Add, Subtract, @@ -873,7 +874,7 @@ impl FunctionDefinition { impl Display for FunctionDefinition { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!(f, "{:?}", self.attributes)?; - write!(f, "fn {} {}", self.signature(), self.body) + write!(f, "{} {}", self.signature(), self.body) } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs index 23832277226..441eff99d9e 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/statement.rs @@ -180,7 +180,7 @@ impl StatementKind { } } -#[derive(Eq, Debug, Clone)] +#[derive(Eq, Debug, Clone, Default)] pub struct Ident(pub Spanned); impl Ident { @@ -333,12 +333,12 @@ impl Display for UseTree { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.prefix)?; + if !self.prefix.segments.is_empty() { + write!(f, "::")?; + } + match &self.kind { UseTreeKind::Path(name, alias) => { - if !(self.prefix.segments.is_empty() && self.prefix.kind == PathKind::Plain) { - write!(f, "::")?; - } - write!(f, "{name}")?; if let Some(alias) = alias { @@ -348,7 +348,7 @@ impl Display for UseTree { Ok(()) } UseTreeKind::List(trees) => { - write!(f, "::{{")?; + write!(f, "{{")?; let tree = vecmap(trees, ToString::to_string).join(", "); write!(f, "{tree}}}") } @@ -467,7 +467,9 @@ impl Path { } pub fn is_ident(&self) -> bool { - self.segments.len() == 1 && self.kind == PathKind::Plain + self.kind == PathKind::Plain + && self.segments.len() == 1 + && self.segments.first().unwrap().generics.is_none() } pub fn as_ident(&self) -> Option<&Ident> { @@ -484,6 +486,10 @@ impl Path { self.segments.first().cloned().map(|segment| segment.ident) } + pub fn is_empty(&self) -> bool { + self.segments.is_empty() && self.kind == PathKind::Plain + } + pub fn as_string(&self) -> String { let mut string = String::new(); @@ -650,14 +656,6 @@ impl Pattern { } } - pub(crate) fn into_ident(self) -> Ident { - match self { - Pattern::Identifier(ident) => ident, - Pattern::Mutable(pattern, _, _) => pattern.into_ident(), - other => panic!("Pattern::into_ident called on {other} pattern with no identifier"), - } - } - pub(crate) fn try_as_expression(&self, interner: &NodeInterner) -> Option { match self { Pattern::Identifier(ident) => Some(Expression { @@ -726,37 +724,36 @@ impl LValue { Expression::new(kind, span) } - pub fn from_expression(expr: Expression) -> LValue { + pub fn from_expression(expr: Expression) -> Option { LValue::from_expression_kind(expr.kind, expr.span) } - pub fn from_expression_kind(expr: ExpressionKind, span: Span) -> LValue { + pub fn from_expression_kind(expr: ExpressionKind, span: Span) -> Option { match expr { - ExpressionKind::Variable(path) => LValue::Ident(path.as_ident().unwrap().clone()), - ExpressionKind::MemberAccess(member_access) => LValue::MemberAccess { - object: Box::new(LValue::from_expression(member_access.lhs)), + ExpressionKind::Variable(path) => Some(LValue::Ident(path.as_ident().unwrap().clone())), + ExpressionKind::MemberAccess(member_access) => Some(LValue::MemberAccess { + object: Box::new(LValue::from_expression(member_access.lhs)?), field_name: member_access.rhs, span, - }, - ExpressionKind::Index(index) => LValue::Index { - array: Box::new(LValue::from_expression(index.collection)), + }), + ExpressionKind::Index(index) => Some(LValue::Index { + array: Box::new(LValue::from_expression(index.collection)?), index: index.index, span, - }, + }), ExpressionKind::Prefix(prefix) => { if matches!( prefix.operator, crate::ast::UnaryOp::Dereference { implicitly_added: false } ) { - LValue::Dereference(Box::new(LValue::from_expression(prefix.rhs)), span) + Some(LValue::Dereference(Box::new(LValue::from_expression(prefix.rhs)?), span)) } else { - panic!("Called LValue::from_expression with an invalid prefix operator") + None } } - ExpressionKind::Interned(id) => LValue::Interned(id, span), - _ => { - panic!("Called LValue::from_expression with an invalid expression") - } + ExpressionKind::Parenthesized(expr) => LValue::from_expression(*expr), + ExpressionKind::Interned(id) => Some(LValue::Interned(id, span)), + _ => None, } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs index d2fa95e4f5a..61ef6f6276d 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/ast/traits.rs @@ -216,7 +216,24 @@ impl Display for TraitBound { impl Display for NoirTraitImpl { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "impl {}{} for {} {{", self.trait_name, self.trait_generics, self.object_type)?; + write!(f, "impl")?; + if !self.impl_generics.is_empty() { + write!( + f, + "<{}>", + self.impl_generics.iter().map(ToString::to_string).collect::>().join(", ") + )?; + } + + write!(f, " {}{} for {}", self.trait_name, self.trait_generics, self.object_type)?; + if !self.where_clause.is_empty() { + write!( + f, + " where {}", + self.where_clause.iter().map(ToString::to_string).collect::>().join(", ") + )?; + } + writeln!(f, "{{")?; for item in self.items.iter() { let item = item.to_string(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs index 66de265f869..fed3149118b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/debug/mod.rs @@ -1,5 +1,6 @@ use crate::ast::PathSegment; -use crate::parser::{parse_program, ParsedModule}; +use crate::parse_program; +use crate::parser::ParsedModule; use crate::{ ast, ast::{Path, PathKind}, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs index 4d6095724f4..426e160206f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/elaborator/comptime.rs @@ -1,6 +1,5 @@ use std::{collections::BTreeMap, fmt::Display}; -use chumsky::Parser; use fm::FileId; use iter_extended::vecmap; use noirc_errors::{Location, Span}; @@ -22,7 +21,7 @@ use crate::{ hir_def::expr::{HirExpression, HirIdent}, lexer::Lexer, node_interner::{DefinitionKind, DependencyId, FuncId, NodeInterner, StructId, TraitId}, - parser::{self, TopLevelStatement, TopLevelStatementKind}, + parser::{Item, ItemKind, Parser}, token::SecondaryAttribute, Type, TypeBindings, UnificationError, }; @@ -261,9 +260,10 @@ impl<'context> Elaborator<'context> { return Err((lexing_errors.swap_remove(0).into(), location.file)); } - let expression = parser::expression() - .parse(tokens) - .map_err(|mut errors| (errors.swap_remove(0).into(), location.file))?; + let Some(expression) = Parser::for_tokens(tokens).parse_option(Parser::parse_expression) + else { + return Ok(None); + }; let (mut func, mut arguments) = match expression.kind { ExpressionKind::Call(call) => (*call.func, call.arguments), @@ -370,7 +370,7 @@ impl<'context> Elaborator<'context> { fn add_items( &mut self, - items: Vec, + items: Vec, generated_items: &mut CollectedItems, location: Location, ) { @@ -381,12 +381,12 @@ impl<'context> Elaborator<'context> { pub(crate) fn add_item( &mut self, - item: TopLevelStatement, + item: Item, generated_items: &mut CollectedItems, location: Location, ) { match item.kind { - TopLevelStatementKind::Function(function) => { + ItemKind::Function(function) => { let module_id = self.module_id(); if let Some(id) = dc_mod::collect_function( @@ -407,7 +407,7 @@ impl<'context> Elaborator<'context> { }); } } - TopLevelStatementKind::TraitImpl(mut trait_impl) => { + ItemKind::TraitImpl(mut trait_impl) => { let (methods, associated_types, associated_constants) = dc_mod::collect_trait_impl_items( self.interner, @@ -437,7 +437,7 @@ impl<'context> Elaborator<'context> { resolved_trait_generics: Vec::new(), }); } - TopLevelStatementKind::Global(global, visibility) => { + ItemKind::Global(global, visibility) => { let (global, error) = dc_mod::collect_global( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), @@ -453,7 +453,7 @@ impl<'context> Elaborator<'context> { self.errors.push(error); } } - TopLevelStatementKind::Struct(struct_def) => { + ItemKind::Struct(struct_def) => { if let Some((type_id, the_struct)) = dc_mod::collect_struct( self.interner, self.def_maps.get_mut(&self.crate_id).unwrap(), @@ -466,20 +466,17 @@ impl<'context> Elaborator<'context> { generated_items.types.insert(type_id, the_struct); } } - TopLevelStatementKind::Impl(r#impl) => { + ItemKind::Impl(r#impl) => { let module = self.module_id(); dc_mod::collect_impl(self.interner, generated_items, r#impl, self.file, module); } - // Assume that an error has already been issued - TopLevelStatementKind::Error => (), - - TopLevelStatementKind::Module(_) - | TopLevelStatementKind::Import(..) - | TopLevelStatementKind::Trait(_) - | TopLevelStatementKind::TypeAlias(_) - | TopLevelStatementKind::SubModule(_) - | TopLevelStatementKind::InnerAttribute(_) => { + ItemKind::ModuleDecl(_) + | ItemKind::Import(..) + | ItemKind::Trait(_) + | ItemKind::TypeAlias(_) + | ItemKind::Submodules(_) + | ItemKind::InnerAttribute(_) => { let item = item.kind.to_string(); let error = InterpreterError::UnsupportedTopLevelItemUnquote { item, location }; self.errors.push(error.into_compilation_error_pair()); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs index 0b028217c46..c80dbb480f5 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin.rs @@ -10,7 +10,6 @@ use builtin_helpers::{ mutate_func_meta_type, parse, quote_ident, replace_func_meta_parameters, replace_func_meta_return_type, }; -use chumsky::{chain::Chain, prelude::choice, primitive::just, Parser}; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::Location; @@ -33,12 +32,10 @@ use crate::{ def_collector::dc_crate::CollectedItems, def_map::ModuleDefId, }, - hir_def::{ - expr::{HirExpression, HirLiteral}, - function::FunctionBody, - }, + hir_def::expr::{HirExpression, HirLiteral}, + hir_def::function::FunctionBody, node_interner::{DefinitionKind, NodeInterner, TraitImplKind}, - parser, + parser::Parser, token::{Attribute, SecondaryAttribute, Token}, Kind, QuotedType, ResolvedGeneric, Shared, Type, TypeVariable, }; @@ -193,6 +190,9 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_as_array" => type_as_array(arguments, return_type, location), "type_as_constant" => type_as_constant(arguments, return_type, location), "type_as_integer" => type_as_integer(arguments, return_type, location), + "type_as_mutable_reference" => { + type_as_mutable_reference(arguments, return_type, location) + } "type_as_slice" => type_as_slice(arguments, return_type, location), "type_as_str" => type_as_str(arguments, return_type, location), "type_as_struct" => type_as_struct(arguments, return_type, location), @@ -205,6 +205,7 @@ impl<'local, 'context> Interpreter<'local, 'context> { "type_implements" => type_implements(interner, arguments, location), "type_is_bool" => type_is_bool(arguments, location), "type_is_field" => type_is_field(arguments, location), + "type_is_unit" => type_is_unit(arguments, location), "type_of" => type_of(arguments, location), "typed_expr_as_function_definition" => { typed_expr_as_function_definition(interner, arguments, return_type, location) @@ -212,7 +213,15 @@ impl<'local, 'context> Interpreter<'local, 'context> { "typed_expr_get_type" => { typed_expr_get_type(interner, arguments, return_type, location) } + "unresolved_type_as_mutable_reference" => { + unresolved_type_as_mutable_reference(interner, arguments, return_type, location) + } + "unresolved_type_as_slice" => { + unresolved_type_as_slice(interner, arguments, return_type, location) + } + "unresolved_type_is_bool" => unresolved_type_is_bool(interner, arguments, location), "unresolved_type_is_field" => unresolved_type_is_field(interner, arguments, location), + "unresolved_type_is_unit" => unresolved_type_is_unit(interner, arguments, location), "zeroed" => zeroed(return_type), _ => { let item = format!("Comptime evaluation for builtin function {name}"); @@ -675,15 +684,24 @@ fn quoted_as_expr( ) -> IResult { let argument = check_one_argument(arguments, location)?; - let expr_parser = parser::expression().map(|expr| Value::expression(expr.kind)); - let statement_parser = parser::fresh_statement().map(Value::statement); - let lvalue_parser = parser::lvalue(parser::expression()).map(Value::lvalue); - let parser = choice((expr_parser, statement_parser, lvalue_parser)); - let parser = parser.then_ignore(just(Token::Semicolon).or_not()); + let result = + parse(interner, argument.clone(), Parser::parse_expression_or_error, "an expression"); + if let Ok(expr) = result { + return option(return_type, Some(Value::expression(expr.kind))); + } + + let result = + parse(interner, argument.clone(), Parser::parse_statement_or_error, "an expression"); + if let Ok(stmt) = result { + return option(return_type, Some(Value::statement(stmt.kind))); + } - let expr = parse(interner, argument, parser, "an expression").ok(); + let result = parse(interner, argument, Parser::parse_lvalue_or_error, "an expression"); + if let Ok(lvalue) = result { + return option(return_type, Some(Value::lvalue(lvalue))); + } - option(return_type, expr) + option(return_type, None) } // fn as_module(quoted: Quoted) -> Option @@ -695,9 +713,13 @@ fn quoted_as_module( ) -> IResult { let argument = check_one_argument(arguments, location)?; - let path = - parse(interpreter.elaborator.interner, argument, parser::path_no_turbofish(), "a path") - .ok(); + let path = parse( + interpreter.elaborator.interner, + argument, + Parser::parse_path_no_turbofish_or_error, + "a path", + ) + .ok(); let option_value = path.and_then(|path| { let module = interpreter .elaborate_in_function(interpreter.current_function, |elaborator| { @@ -719,7 +741,7 @@ fn quoted_as_trait_constraint( let trait_bound = parse( interpreter.elaborator.interner, argument, - parser::trait_bound(), + Parser::parse_trait_bound_or_error, "a trait constraint", )?; let bound = interpreter @@ -738,7 +760,8 @@ fn quoted_as_type( location: Location, ) -> IResult { let argument = check_one_argument(arguments, location)?; - let typ = parse(interpreter.elaborator.interner, argument, parser::parse_type(), "a type")?; + let typ = + parse(interpreter.elaborator.interner, argument, Parser::parse_type_or_error, "a type")?; let typ = interpreter .elaborate_in_function(interpreter.current_function, |elab| elab.resolve_type(typ)); Ok(Value::Type(typ)) @@ -855,6 +878,21 @@ fn type_as_integer( }) } +// fn as_mutable_reference(self) -> Option +fn type_as_mutable_reference( + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + type_as(arguments, return_type, location, |typ| { + if let Type::MutableReference(typ) = typ { + Some(Value::Type(*typ)) + } else { + None + } + }) +} + // fn as_slice(self) -> Option fn type_as_slice( arguments: Vec<(Value, Location)>, @@ -1013,6 +1051,14 @@ fn type_is_field(arguments: Vec<(Value, Location)>, location: Location) -> IResu Ok(Value::Bool(matches!(typ, Type::FieldElement))) } +// fn is_unit(self) -> bool +fn type_is_unit(arguments: Vec<(Value, Location)>, location: Location) -> IResult { + let value = check_one_argument(arguments, location)?; + let typ = get_type(value)?; + + Ok(Value::Bool(matches!(typ, Type::Unit))) +} + // fn type_of(x: T) -> Type fn type_of(arguments: Vec<(Value, Location)>, location: Location) -> IResult { let (value, _) = check_one_argument(arguments, location)?; @@ -1115,6 +1161,49 @@ fn typed_expr_get_type( option(return_type, option_value) } +// fn as_mutable_reference(self) -> Option +fn unresolved_type_as_mutable_reference( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + unresolved_type_as(interner, arguments, return_type, location, |typ| { + if let UnresolvedTypeData::MutableReference(typ) = typ { + Some(Value::UnresolvedType(typ.typ)) + } else { + None + } + }) +} + +// fn as_slice(self) -> Option +fn unresolved_type_as_slice( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, +) -> IResult { + unresolved_type_as(interner, arguments, return_type, location, |typ| { + if let UnresolvedTypeData::Slice(typ) = typ { + Some(Value::UnresolvedType(typ.typ)) + } else { + None + } + }) +} + +// fn is_bool(self) -> bool +fn unresolved_type_is_bool( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::Bool))) +} + // fn is_field(self) -> bool fn unresolved_type_is_field( interner: &NodeInterner, @@ -1126,6 +1215,36 @@ fn unresolved_type_is_field( Ok(Value::Bool(matches!(typ, UnresolvedTypeData::FieldElement))) } +// fn is_unit(self) -> bool +fn unresolved_type_is_unit( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + location: Location, +) -> IResult { + let self_argument = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, self_argument)?; + Ok(Value::Bool(matches!(typ, UnresolvedTypeData::Unit))) +} + +// Helper function for implementing the `unresolved_type_as_...` functions. +fn unresolved_type_as( + interner: &NodeInterner, + arguments: Vec<(Value, Location)>, + return_type: Type, + location: Location, + f: F, +) -> IResult +where + F: FnOnce(UnresolvedTypeData) -> Option, +{ + let value = check_one_argument(arguments, location)?; + let typ = get_unresolved_type(interner, value)?; + + let option_value = f(typ); + + option(return_type, option_value) +} + // fn zeroed() -> T fn zeroed(return_type: Type) -> IResult { match return_type { @@ -2325,7 +2444,7 @@ fn function_def_set_parameters( let parameter_pattern = parse( interpreter.elaborator.interner, (tuple.pop().unwrap(), parameters_argument_location), - parser::pattern(), + Parser::parse_pattern_or_error, "a pattern", )?; @@ -2426,7 +2545,7 @@ fn module_add_item( let module_id = get_module(self_argument)?; let module_data = interpreter.elaborator.get_module(module_id); - let parser = parser::top_level_items(); + let parser = Parser::parse_top_level_items; let top_level_statements = parse(interpreter.elaborator.interner, item, parser, "a top-level item")?; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs index a355b23b74f..3f9d92cfe88 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/interpreter/builtin/builtin_helpers.rs @@ -5,7 +5,9 @@ use acvm::FieldElement; use noirc_errors::Location; use crate::hir::comptime::display::tokens_to_string; +use crate::hir::comptime::value::add_token_spans; use crate::lexer::Lexer; +use crate::parser::Parser; use crate::{ ast::{ BlockExpression, ExpressionKind, Ident, IntegerBitSize, LValue, Pattern, Signedness, @@ -14,7 +16,7 @@ use crate::{ hir::{ comptime::{ errors::IResult, - value::{add_token_spans, ExprValue, TypedExpr}, + value::{ExprValue, TypedExpr}, Interpreter, InterpreterError, Value, }, def_map::ModuleId, @@ -25,7 +27,6 @@ use crate::{ stmt::HirPattern, }, node_interner::{FuncId, NodeInterner, StructId, TraitId, TraitImplId}, - parser::NoirParser, token::{SecondaryAttribute, Token, Tokens}, QuotedType, Type, }; @@ -402,27 +403,32 @@ pub(super) fn lex(input: &str) -> Vec { tokens } -pub(super) fn parse( +pub(super) fn parse<'a, T, F>( interner: &NodeInterner, (value, location): (Value, Location), - parser: impl NoirParser, + parser: F, rule: &'static str, -) -> IResult { - let parser = parser.then_ignore(chumsky::primitive::end()); +) -> IResult +where + F: FnOnce(&mut Parser<'a>) -> T, +{ let tokens = get_quoted((value, location))?; let quoted = add_token_spans(tokens.clone(), location.span); parse_tokens(tokens, quoted, interner, location, parser, rule) } -pub(super) fn parse_tokens( +pub(super) fn parse_tokens<'a, T, F>( tokens: Rc>, quoted: Tokens, interner: &NodeInterner, location: Location, - parser: impl NoirParser, + parsing_function: F, rule: &'static str, -) -> IResult { - parser.parse(quoted).map_err(|mut errors| { +) -> IResult +where + F: FnOnce(&mut Parser<'a>) -> T, +{ + Parser::for_tokens(quoted).parse_result(parsing_function).map_err(|mut errors| { let error = errors.swap_remove(0); let tokens = tokens_to_string(tokens, interner); InterpreterError::FailedToParseMacro { error, tokens, rule, file: location.file } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs index 310c4a78414..458a186a3f8 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/tests.rs @@ -14,7 +14,7 @@ use crate::hir::def_collector::dc_crate::DefCollector; use crate::hir::def_collector::dc_mod::collect_defs; use crate::hir::def_map::{CrateDefMap, LocalModuleId, ModuleData}; use crate::hir::{Context, ParsedFiles}; -use crate::parser::parse_program; +use crate::parse_program; fn interpret_helper(src: &str) -> Result { let file = FileId::default(); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs index 4c968234f04..4b55735fcb1 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/comptime/value.rs @@ -1,7 +1,6 @@ use std::{borrow::Cow, rc::Rc, vec}; use acvm::{AcirField, FieldElement}; -use chumsky::Parser; use im::Vector; use iter_extended::{try_vecmap, vecmap}; use noirc_errors::{Location, Span}; @@ -19,7 +18,7 @@ use crate::{ ImplKind, }, node_interner::{ExprId, FuncId, NodeInterner, StmtId, StructId, TraitId, TraitImplId}, - parser::{self, NoirParser, TopLevelStatement}, + parser::{Item, Parser}, token::{SpannedToken, Token, Tokens}, Kind, QuotedType, Shared, Type, TypeBindings, }; @@ -261,7 +260,8 @@ impl Value { tokens_to_parse.0.insert(0, SpannedToken::new(Token::LeftBrace, location.span)); tokens_to_parse.0.push(SpannedToken::new(Token::RightBrace, location.span)); - return match parser::expression().parse(tokens_to_parse) { + let parser = Parser::for_tokens(tokens_to_parse); + return match parser.parse_result(Parser::parse_expression_or_error) { Ok(expr) => Ok(expr), Err(mut errors) => { let error = errors.swap_remove(0); @@ -523,8 +523,8 @@ impl Value { self, location: Location, interner: &NodeInterner, - ) -> IResult> { - let parser = parser::top_level_items(); + ) -> IResult> { + let parser = Parser::parse_top_level_items; match self { Value::Quoted(tokens) => { parse_tokens(tokens, interner, parser, location, "top-level item") @@ -543,15 +543,18 @@ pub(crate) fn unwrap_rc(rc: Rc) -> T { Rc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()) } -fn parse_tokens( +fn parse_tokens<'a, T, F>( tokens: Rc>, interner: &NodeInterner, - parser: impl NoirParser, + parsing_function: F, location: Location, rule: &'static str, -) -> IResult { - let parser = parser.then_ignore(chumsky::primitive::end()); - match parser.parse(add_token_spans(tokens.clone(), location.span)) { +) -> IResult +where + F: FnOnce(&mut Parser<'a>) -> T, +{ + let parser = Parser::for_tokens(add_token_spans(tokens.clone(), location.span)); + match parser.parse_result(parsing_function) { Ok(expr) => Ok(expr), Err(mut errors) => { let error = errors.swap_remove(0); diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs index e9dfc5ac067..60ee2c52842 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir/def_map/mod.rs @@ -2,7 +2,8 @@ use crate::graph::CrateId; use crate::hir::def_collector::dc_crate::{CompilationError, DefCollector}; use crate::hir::Context; use crate::node_interner::{FuncId, GlobalId, NodeInterner, StructId}; -use crate::parser::{parse_program, ParsedModule, ParserError}; +use crate::parse_program; +use crate::parser::{ParsedModule, ParserError}; use crate::token::{FunctionAttribute, SecondaryAttribute, TestScope}; use fm::{FileId, FileManager}; use noirc_arena::{Arena, Index}; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs index b8c98428bb0..5c2322acfda 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/hir_def/types.rs @@ -244,6 +244,7 @@ impl std::fmt::Display for Kind { } #[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, PartialOrd, Ord)] +#[cfg_attr(test, derive(strum_macros::EnumIter))] pub enum QuotedType { Expr, Quoted, diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs index 6e64c509195..66f79bd444b 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/errors.rs @@ -143,10 +143,3 @@ impl<'a> From<&'a LexerErrorKind> for Diagnostic { Diagnostic::simple_error(primary, secondary, span) } } - -impl From for chumsky::error::Simple { - fn from(error: LexerErrorKind) -> Self { - let (_, message, span) = error.parts(); - chumsky::error::Simple::custom(span, message) - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs index 626c46d392f..d79a184d4c4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/lexer/token.rs @@ -1,6 +1,6 @@ use acvm::{acir::AcirField, FieldElement}; use noirc_errors::{Position, Span, Spanned}; -use std::{fmt, iter::Map, vec::IntoIter}; +use std::fmt; use crate::{ lexer::errors::LexerErrorKind, @@ -1227,25 +1227,6 @@ impl Keyword { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Tokens(pub Vec); -type TokenMapIter = Map, fn(SpannedToken) -> (Token, Span)>; - -impl<'a> From for chumsky::Stream<'a, Token, Span, TokenMapIter> { - fn from(tokens: Tokens) -> Self { - let end_of_input = match tokens.0.last() { - Some(spanned_token) => spanned_token.to_span(), - None => Span::single_char(0), - }; - - fn get_span(token: SpannedToken) -> (Token, Span) { - let span = token.to_span(); - (token.into_token(), span) - } - - let iter = tokens.0.into_iter().map(get_span as fn(_) -> _); - chumsky::Stream::from_iter(end_of_input, iter) - } -} - #[cfg(test)] mod keywords { use strum::IntoEnumIterator; diff --git a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs index 6a7096c10c2..b80c37c2ce4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/node_interner.rs @@ -2145,6 +2145,7 @@ impl NodeInterner { pub fn get_lvalue(&self, id: InternedExpressionKind, span: Span) -> LValue { LValue::from_expression_kind(self.get_expression_kind(id).clone(), span) + .expect("Called LValue::from_expression with an invalid expression") } pub fn push_pattern(&mut self, pattern: Pattern) -> InternedPattern { diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs index 75fe1bf747f..f9cc539d7b7 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/errors.rs @@ -1,4 +1,4 @@ -use crate::ast::{Expression, IntegerBitSize}; +use crate::ast::{Expression, IntegerBitSize, ItemVisibility}; use crate::lexer::errors::LexerErrorKind; use crate::lexer::token::Token; use crate::token::TokenKind; @@ -13,26 +13,50 @@ use super::labels::ParsingRuleLabel; #[derive(Debug, Clone, PartialEq, Eq, Error)] pub enum ParserErrorReason { - #[error("Unexpected '{0}', expected a field name")] + #[error("Unexpected `;`")] + UnexpectedSemicolon, + #[error("Unexpected `,`")] + UnexpectedComma, + #[error("Expected a `{token}` separating these two {items}")] + ExpectedTokenSeparatingTwoItems { token: Token, items: &'static str }, + #[error("Invalid left-hand side of assignment")] + InvalidLeftHandSideOfAssignment, + #[error("Expected trait, found {found}")] + ExpectedTrait { found: String }, + #[error("Visibility `{visibility}` is not followed by an item")] + VisibilityNotFollowedByAnItem { visibility: ItemVisibility }, + #[error("`unconstrained` is not followed by an item")] + UnconstrainedNotFollowedByAnItem, + #[error("`comptime` is not followed by an item")] + ComptimeNotFollowedByAnItem, + #[error("`mut` cannot be applied to this item")] + MutableNotApplicable, + #[error("`comptime` cannot be applied to this item")] + ComptimeNotApplicable, + #[error("`unconstrained` cannot be applied to this item")] + UnconstrainedNotApplicable, + #[error("Expected an identifier or `(expression) after `$` for unquoting")] + ExpectedIdentifierOrLeftParenAfterDollar, + #[error("`&mut` can only be used with `self")] + RefMutCanOnlyBeUsedWithSelf, + #[error("Invalid pattern")] + InvalidPattern, + #[error("Documentation comment does not document anything")] + DocCommentDoesNotDocumentAnything, + + #[error("Missing type for function parameter")] + MissingTypeForFunctionParameter, + #[error("Missing type for numeric generic")] + MissingTypeForNumericGeneric, + #[error("Expected a function body (`{{ ... }}`), not `;`")] + ExpectedFunctionBody, + #[error("Expected the global to have a value")] + GlobalWithoutValue, + + #[error("Unexpected '{0}', expected a field name or number")] ExpectedFieldName(Token), - #[error("expected a pattern but found a type - {0}")] + #[error("Expected a pattern but found a type - {0}")] ExpectedPatternButFoundType(Token), - #[error("expected an identifier after .")] - ExpectedIdentifierAfterDot, - #[error("expected an identifier after ::")] - ExpectedIdentifierAfterColons, - #[error("expected {{ or -> after function parameters")] - ExpectedLeftBraceOrArrowAfterFunctionParameters, - #[error("expected {{ after if condition")] - ExpectedLeftBraceAfterIfCondition, - #[error("expected <, where or {{ after trait name")] - ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitName, - #[error("expected <, where or {{ after impl type")] - ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterImplType, - #[error("expected <, where or {{ after trait impl for type")] - ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitImplForType, - #[error("expected ( or < after function name")] - ExpectedLeftParenOrLeftBracketAfterFunctionName, #[error("Expected a ; separating these two statements")] MissingSeparatingSemi, #[error("constrain keyword is deprecated")] @@ -105,6 +129,20 @@ impl ParserError { } } + pub fn expected_token(token: Token, found: Token, span: Span) -> ParserError { + let mut error = ParserError::empty(found, span); + error.expected_tokens.insert(token); + error + } + + pub fn expected_one_of_tokens(tokens: &[Token], found: Token, span: Span) -> ParserError { + let mut error = ParserError::empty(found, span); + for token in tokens { + error.expected_tokens.insert(token.clone()); + } + error + } + pub fn expected_label(label: ParsingRuleLabel, found: Token, span: Span) -> ParserError { let mut error = ParserError::empty(found, span); error.expected_labels.insert(label); @@ -206,7 +244,7 @@ impl<'a> From<&'a ParserError> for Diagnostic { Diagnostic::simple_warning(reason.to_string(), "".into(), error.span) } ParserErrorReason::ExpectedPatternButFoundType(ty) => Diagnostic::simple_error( - "Expected a ; separating these two statements".into(), + format!("Expected a pattern but found a type - {ty}"), format!("{ty} is a type and cannot be used as a variable name"), error.span, ), @@ -229,45 +267,3 @@ impl<'a> From<&'a ParserError> for Diagnostic { } } } - -impl chumsky::Error for ParserError { - type Span = Span; - type Label = ParsingRuleLabel; - - fn expected_input_found(span: Self::Span, expected: Iter, found: Option) -> Self - where - Iter: IntoIterator>, - { - ParserError { - expected_tokens: expected.into_iter().map(|opt| opt.unwrap_or(Token::EOF)).collect(), - expected_labels: SmallOrdSet::new(), - found: found.unwrap_or(Token::EOF), - reason: None, - span, - } - } - - fn with_label(mut self, label: Self::Label) -> Self { - self.expected_tokens.clear(); - self.expected_labels.clear(); - self.expected_labels.insert(label); - self - } - - // Merge two errors into a new one that should encompass both. - // If one error has a more specific reason with it then keep - // that reason and discard the other if present. - // The spans of both errors must match, otherwise the error - // messages and error spans may not line up. - fn merge(mut self, mut other: Self) -> Self { - self.expected_tokens.append(&mut other.expected_tokens); - self.expected_labels.append(&mut other.expected_labels); - - if self.reason.is_none() { - self.reason = other.reason; - } - - self.span = self.span.merge(other.span); - self - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs index fd082dfbe56..5c9ec236e07 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/labels.rs @@ -11,14 +11,26 @@ pub enum ParsingRuleLabel { Cast, Expression, FieldAccess, + GenericParameter, Global, + Identifier, + Integer, IntegerType, + Item, + LValue, Parameter, + Path, Pattern, Statement, Term, + TraitBound, + TraitImplItem, + TraitItem, + Type, TypeExpression, + TypeOrTypeExpression, TokenKind(TokenKind), + UseSegment, } impl fmt::Display for ParsingRuleLabel { @@ -29,14 +41,26 @@ impl fmt::Display for ParsingRuleLabel { ParsingRuleLabel::Cast => write!(f, "cast"), ParsingRuleLabel::Expression => write!(f, "expression"), ParsingRuleLabel::FieldAccess => write!(f, "field access"), + ParsingRuleLabel::GenericParameter => write!(f, "generic parameter"), ParsingRuleLabel::Global => write!(f, "global"), + ParsingRuleLabel::Identifier => write!(f, "identifier"), + ParsingRuleLabel::Integer => write!(f, "integer"), ParsingRuleLabel::IntegerType => write!(f, "integer type"), + ParsingRuleLabel::Item => write!(f, "item"), + ParsingRuleLabel::LValue => write!(f, "left-hand side of assignment"), ParsingRuleLabel::Parameter => write!(f, "parameter"), + ParsingRuleLabel::Path => write!(f, "path"), ParsingRuleLabel::Pattern => write!(f, "pattern"), ParsingRuleLabel::Statement => write!(f, "statement"), ParsingRuleLabel::Term => write!(f, "term"), + ParsingRuleLabel::TraitBound => write!(f, "trait bound"), + ParsingRuleLabel::TraitImplItem => write!(f, "trait impl item"), + ParsingRuleLabel::TraitItem => write!(f, "trait item"), + ParsingRuleLabel::Type => write!(f, "type"), ParsingRuleLabel::TypeExpression => write!(f, "type expression"), - ParsingRuleLabel::TokenKind(token_kind) => write!(f, "{token_kind:?}"), + ParsingRuleLabel::TypeOrTypeExpression => write!(f, "type or type expression"), + ParsingRuleLabel::TokenKind(token_kind) => write!(f, "{token_kind}"), + ParsingRuleLabel::UseSegment => write!(f, "identifier, `crate`, `dep` or `super`"), } } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs index ad6dd1459e9..21c182a52cd 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/mod.rs @@ -12,233 +12,15 @@ mod labels; mod parser; use crate::ast::{ - Documented, Expression, Ident, ImportStatement, ItemVisibility, LetStatement, - ModuleDeclaration, NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, - Recoverable, StatementKind, TypeImpl, UseTree, + Documented, Ident, ImportStatement, ItemVisibility, LetStatement, ModuleDeclaration, + NoirFunction, NoirStruct, NoirTrait, NoirTraitImpl, NoirTypeAlias, TypeImpl, UseTree, }; -use crate::token::{Keyword, SecondaryAttribute, Token}; +use crate::token::SecondaryAttribute; -use chumsky::prelude::*; -use chumsky::primitive::Container; pub use errors::ParserError; pub use errors::ParserErrorReason; use noirc_errors::Span; -pub use parser::path::path_no_turbofish; -pub use parser::traits::trait_bound; -pub use parser::{ - block, expression, fresh_statement, lvalue, module, parse_program, parse_type, pattern, - top_level_items, top_level_statement, visibility, -}; - -#[derive(Debug, Clone)] -pub struct TopLevelStatement { - pub kind: TopLevelStatementKind, - pub doc_comments: Vec, -} - -#[derive(Debug, Clone)] -pub enum TopLevelStatementKind { - Function(NoirFunction), - Module(ModuleDeclaration), - Import(UseTree, ItemVisibility), - Struct(NoirStruct), - Trait(NoirTrait), - TraitImpl(NoirTraitImpl), - Impl(TypeImpl), - TypeAlias(NoirTypeAlias), - SubModule(ParsedSubModule), - Global(LetStatement, ItemVisibility), - InnerAttribute(SecondaryAttribute), - Error, -} - -impl TopLevelStatementKind { - pub fn into_item_kind(self) -> Option { - match self { - TopLevelStatementKind::Function(f) => Some(ItemKind::Function(f)), - TopLevelStatementKind::Module(m) => Some(ItemKind::ModuleDecl(m)), - TopLevelStatementKind::Import(i, visibility) => Some(ItemKind::Import(i, visibility)), - TopLevelStatementKind::Struct(s) => Some(ItemKind::Struct(s)), - TopLevelStatementKind::Trait(t) => Some(ItemKind::Trait(t)), - TopLevelStatementKind::TraitImpl(t) => Some(ItemKind::TraitImpl(t)), - TopLevelStatementKind::Impl(i) => Some(ItemKind::Impl(i)), - TopLevelStatementKind::TypeAlias(t) => Some(ItemKind::TypeAlias(t)), - TopLevelStatementKind::SubModule(s) => Some(ItemKind::Submodules(s)), - TopLevelStatementKind::Global(c, visibility) => Some(ItemKind::Global(c, visibility)), - TopLevelStatementKind::InnerAttribute(a) => Some(ItemKind::InnerAttribute(a)), - TopLevelStatementKind::Error => None, - } - } -} - -// Helper trait that gives us simpler type signatures for return types: -// e.g. impl Parser versus impl Parser> -pub trait NoirParser: Parser + Sized + Clone {} -impl NoirParser for P where P: Parser + Clone {} - -// ExprParser just serves as a type alias for NoirParser + Clone -pub trait ExprParser: NoirParser {} -impl

ExprParser for P where P: NoirParser {} - -fn parenthesized(parser: P) -> impl NoirParser -where - P: NoirParser, - T: Recoverable, -{ - use Token::*; - parser.delimited_by(just(LeftParen), just(RightParen)).recover_with(nested_delimiters( - LeftParen, - RightParen, - [(LeftBracket, RightBracket)], - Recoverable::error, - )) -} - -fn spanned(parser: P) -> impl NoirParser<(T, Span)> -where - P: NoirParser, -{ - parser.map_with_span(|value, span| (value, span)) -} - -// Parse with the first parser, then continue by -// repeating the second parser 0 or more times. -// The passed in function is then used to combine the -// results of both parsers along with their spans at -// each step. -fn foldl_with_span( - first_parser: P1, - to_be_repeated: P2, - f: F, -) -> impl NoirParser -where - P1: NoirParser, - P2: NoirParser, - F: Fn(T1, T2, Span) -> T1 + Clone, -{ - spanned(first_parser) - .then(spanned(to_be_repeated).repeated()) - .foldl(move |(a, a_span), (b, b_span)| { - let span = a_span.merge(b_span); - (f(a, b, span), span) - }) - .map(|(value, _span)| value) -} - -/// Sequence the two parsers. -/// Fails if the first parser fails, otherwise forces -/// the second parser to succeed while logging any errors. -fn then_commit<'a, P1, P2, T1, T2>( - first_parser: P1, - second_parser: P2, -) -> impl NoirParser<(T1, T2)> + 'a -where - P1: NoirParser + 'a, - P2: NoirParser + 'a, - T2: Clone + Recoverable + 'a, -{ - let second_parser = skip_then_retry_until(second_parser) - .map_with_span(|option, span| option.unwrap_or_else(|| Recoverable::error(span))); - - first_parser.then(second_parser) -} - -fn then_commit_ignore<'a, P1, P2, T1, T2>( - first_parser: P1, - second_parser: P2, -) -> impl NoirParser + 'a -where - P1: NoirParser + 'a, - P2: NoirParser + 'a, - T1: 'a, - T2: Clone + 'a, -{ - let second_parser = skip_then_retry_until(second_parser); - first_parser.then_ignore(second_parser) -} - -fn ignore_then_commit<'a, P1, P2, T1: 'a, T2: Clone + 'a>( - first_parser: P1, - second_parser: P2, -) -> impl NoirParser + 'a -where - P1: NoirParser + 'a, - P2: NoirParser + 'a, - T2: Recoverable, -{ - let second_parser = skip_then_retry_until(second_parser) - .map_with_span(|option, span| option.unwrap_or_else(|| Recoverable::error(span))); - - first_parser.ignore_then(second_parser) -} - -fn skip_then_retry_until<'a, P, T>(parser: P) -> impl NoirParser> + 'a -where - P: NoirParser + 'a, - T: Clone + 'a, -{ - let terminators = [ - Token::EOF, - Token::Colon, - Token::Semicolon, - Token::RightBrace, - Token::Keyword(Keyword::Let), - Token::Keyword(Keyword::Constrain), - ]; - force(parser.recover_with(chumsky::prelude::skip_then_retry_until(terminators))) -} - -/// General recovery strategy: try to skip to the target token, failing if we encounter the -/// 'too_far' token beforehand. -/// -/// Expects all of `too_far` to be contained within `targets` -fn try_skip_until(targets: C1, too_far: C2) -> impl NoirParser -where - T: Recoverable + Clone, - C1: Container + Clone, - C2: Container + Clone, -{ - chumsky::prelude::none_of(targets) - .repeated() - .ignore_then(one_of(too_far.clone()).rewind()) - .try_map(move |peek, span| { - if too_far.get_iter().any(|t| t == peek) { - // This error will never be shown to the user - Err(ParserError::empty(Token::EOF, span)) - } else { - Ok(Recoverable::error(span)) - } - }) -} - -/// Recovery strategy for statements: If a statement fails to parse skip until the next ';' or fail -/// if we find a '}' first. -fn statement_recovery() -> impl NoirParser { - use Token::*; - try_skip_until([Semicolon, RightBrace], RightBrace) -} - -fn parameter_recovery() -> impl NoirParser { - use Token::*; - try_skip_until([Comma, RightParen], RightParen) -} - -fn parameter_name_recovery() -> impl NoirParser { - use Token::*; - try_skip_until([Colon, RightParen, Comma], [RightParen, Comma]) -} - -fn top_level_statement_recovery() -> impl NoirParser { - none_of([Token::RightBrace, Token::EOF]) - .repeated() - .ignore_then(one_of([Token::Semicolon])) - .map(|_| TopLevelStatementKind::Error) -} - -/// Force the given parser to succeed, logging any errors it had -fn force<'a, T: 'a>(parser: impl NoirParser + 'a) -> impl NoirParser> + 'a { - parser.map(Some).recover_via(empty().map(|_| None)) -} +pub use parser::{parse_program, Parser}; #[derive(Clone, Default)] pub struct SortedModule { @@ -362,6 +144,35 @@ pub enum ItemKind { InnerAttribute(SecondaryAttribute), } +impl std::fmt::Display for ItemKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ItemKind::Function(fun) => fun.fmt(f), + ItemKind::ModuleDecl(m) => m.fmt(f), + ItemKind::Import(tree, visibility) => { + if visibility == &ItemVisibility::Private { + write!(f, "use {tree}") + } else { + write!(f, "{visibility} use {tree}") + } + } + ItemKind::Trait(t) => t.fmt(f), + ItemKind::TraitImpl(i) => i.fmt(f), + ItemKind::Struct(s) => s.fmt(f), + ItemKind::Impl(i) => i.fmt(f), + ItemKind::TypeAlias(t) => t.fmt(f), + ItemKind::Submodules(s) => s.fmt(f), + ItemKind::Global(c, visibility) => { + if visibility != &ItemVisibility::Private { + write!(f, "{visibility} ")?; + } + c.fmt(f) + } + ItemKind::InnerAttribute(a) => write!(f, "#![{}]", a), + } + } +} + /// A submodule defined via `mod name { contents }` in some larger file. /// These submodules always share the same file as some larger ParsedModule #[derive(Clone, Debug)] @@ -453,112 +264,6 @@ impl SortedModule { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd)] -pub enum Precedence { - Lowest, - Or, - And, - Xor, - LessGreater, - Shift, - Sum, - Product, - Highest, -} - -impl Precedence { - // Higher the number, the higher(more priority) the precedence - // XXX: Check the precedence is correct for operators - fn token_precedence(tok: &Token) -> Option { - let precedence = match tok { - Token::Equal => Precedence::Lowest, - Token::NotEqual => Precedence::Lowest, - Token::Pipe => Precedence::Or, - Token::Ampersand => Precedence::And, - Token::Caret => Precedence::Xor, - Token::Less => Precedence::LessGreater, - Token::LessEqual => Precedence::LessGreater, - Token::Greater => Precedence::LessGreater, - Token::GreaterEqual => Precedence::LessGreater, - Token::ShiftLeft => Precedence::Shift, - Token::ShiftRight => Precedence::Shift, - Token::Plus => Precedence::Sum, - Token::Minus => Precedence::Sum, - Token::Slash => Precedence::Product, - Token::Star => Precedence::Product, - Token::Percent => Precedence::Product, - _ => return None, - }; - - assert_ne!(precedence, Precedence::Highest, "expression_with_precedence in the parser currently relies on the highest precedence level being uninhabited"); - Some(precedence) - } - - /// Return the next higher precedence. E.g. `Sum.next() == Product` - fn next(self) -> Self { - use Precedence::*; - match self { - Lowest => Or, - Or => Xor, - Xor => And, - And => LessGreater, - LessGreater => Shift, - Shift => Sum, - Sum => Product, - Product => Highest, - Highest => Highest, - } - } - - /// TypeExpressions only contain basic arithmetic operators and - /// notably exclude `>` due to parsing conflicts with generic type brackets. - fn next_type_precedence(self) -> Self { - use Precedence::*; - match self { - Lowest => Sum, - Sum => Product, - Product => Highest, - Highest => Highest, - other => unreachable!("Unexpected precedence level in type expression: {:?}", other), - } - } - - /// The operators with the lowest precedence still useable in type expressions - /// are '+' and '-' with precedence Sum. - fn lowest_type_precedence() -> Self { - Precedence::Sum - } -} - -impl std::fmt::Display for TopLevelStatementKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TopLevelStatementKind::Function(fun) => fun.fmt(f), - TopLevelStatementKind::Module(m) => m.fmt(f), - TopLevelStatementKind::Import(tree, visibility) => { - if visibility != &ItemVisibility::Private { - write!(f, "{visibility} ")?; - } - write!(f, "use {tree}") - } - TopLevelStatementKind::Trait(t) => t.fmt(f), - TopLevelStatementKind::TraitImpl(i) => i.fmt(f), - TopLevelStatementKind::Struct(s) => s.fmt(f), - TopLevelStatementKind::Impl(i) => i.fmt(f), - TopLevelStatementKind::TypeAlias(t) => t.fmt(f), - TopLevelStatementKind::SubModule(s) => s.fmt(f), - TopLevelStatementKind::Global(c, visibility) => { - if visibility != &ItemVisibility::Private { - write!(f, "{visibility} ")?; - } - c.fmt(f) - } - TopLevelStatementKind::InnerAttribute(a) => write!(f, "#![{}]", a), - TopLevelStatementKind::Error => write!(f, "error"), - } - } -} - impl std::fmt::Display for ParsedModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.clone().into_sorted().fmt(f) diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs index 60128c6cafe..d0b0579ce24 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser.rs @@ -1,88 +1,41 @@ -//! This file contains the bulk of the implementation of noir's parser. -//! -//! Noir's parser is built off the [chumsky library](https://docs.rs/chumsky/latest/chumsky/) -//! for parser combinators. In this technique, parsers are built from smaller parsers that -//! parse e.g. only a single token. Then there are functions which can combine multiple -//! parsers together to create a larger one. These functions are called parser combinators. -//! For example, `a.then(b)` combines two parsers a and b and returns one that parses a -//! then parses b and fails if either fails. Other combinators like `a.or(b)` exist as well -//! and are used extensively. Note that these form a PEG grammar so if there are multiple -//! options as in `a.or(b)` the first matching parse will be chosen. -//! -//! Noir's grammar is not formally specified but can be estimated by inspecting each function. -//! For example, a function `f` parsing `choice((a, b, c))` can be roughly translated to -//! BNF as `f: a | b | c`. -//! -//! Occasionally there will also be recovery strategies present, either via `recover_via(Parser)` -//! or `recover_with(Strategy)`. The difference between the two functions isn't quite so important, -//! but both allow the parser to recover from a parsing error, log the error, and return an error -//! expression instead. These are used to parse cases such as `fn foo( { }` where we know the user -//! meant to write a function and thus we should log the error and return a function with no parameters, -//! rather than failing to parse a function and trying to subsequently parse a struct. Generally using -//! recovery strategies improves parser errors but using them incorrectly can be problematic since they -//! prevent other parsers from being tried afterward since there is no longer an error. Thus, they should -//! be limited to cases like the above `fn` example where it is clear we shouldn't back out of the -//! current parser to try alternative parsers in a `choice` expression. -use self::path::{as_trait_path, type_path}; -use self::primitives::{ - interned_statement, interned_statement_expr, keyword, macro_quote_marker, mutable_reference, - variable, +use acvm::FieldElement; +use modifiers::Modifiers; +use noirc_errors::Span; + +use crate::{ + ast::{Ident, ItemVisibility, LValue}, + lexer::{Lexer, SpannedTokenResult}, + token::{IntType, Keyword, SpannedToken, Token, TokenKind, Tokens}, }; -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; - -use super::{ - foldl_with_span, labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, - parenthesized, then_commit, then_commit_ignore, top_level_statement_recovery, ExprParser, - NoirParser, ParsedModule, ParsedSubModule, ParserError, ParserErrorReason, Precedence, - TopLevelStatementKind, -}; -use super::{spanned, Item, TopLevelStatement}; -use crate::ast::{ - BinaryOp, BinaryOpKind, BlockExpression, Documented, ForLoopStatement, ForRange, - GenericTypeArgs, Ident, IfExpression, InfixExpression, LValue, Literal, ModuleDeclaration, - NoirTypeAlias, Param, Path, Pattern, Recoverable, Statement, TypeImpl, UnaryRhsMemberAccess, - UnaryRhsMethodCall, UseTree, UseTreeKind, Visibility, -}; -use crate::ast::{ - Expression, ExpressionKind, LetStatement, StatementKind, UnresolvedType, UnresolvedTypeData, -}; -use crate::lexer::Lexer; -use crate::parser::{force, ignore_then_commit, statement_recovery}; -use crate::token::{Keyword, Token, TokenKind}; -use acvm::AcirField; -use chumsky::prelude::*; -use iter_extended::vecmap; -use noirc_errors::{Span, Spanned}; +use super::{labels::ParsingRuleLabel, ParsedModule, ParserError, ParserErrorReason}; -mod assertion; +mod arguments; mod attributes; mod doc_comments; +mod expression; mod function; -mod lambdas; -mod literals; -pub(super) mod path; -mod primitives; +mod generics; +mod global; +mod impls; +mod infix; +mod item; +mod item_visibility; +mod lambda; +mod modifiers; +mod module; +mod parse_many; +mod path; +mod pattern; +mod statement; mod structs; -pub(super) mod traits; +mod tests; +mod traits; +mod type_alias; +mod type_expression; mod types; -mod visibility; - -#[cfg(test)] -mod test_helpers; - -use literals::literal; -use path::{maybe_empty_path, path}; -use primitives::{ - dereference, ident, interned_expr, negation, not, nothing, right_shift_operator, token_kind, -}; -use traits::where_clause; +mod use_tree; +mod where_clause; /// Entry function for the parser - also handles lexing internally. /// @@ -91,1764 +44,513 @@ use traits::where_clause; /// Vec is non-empty, there may be Error nodes in the Ast to fill in the gaps that /// failed to parse. Otherwise the Ast is guaranteed to have 0 Error nodes. pub fn parse_program(source_program: &str) -> (ParsedModule, Vec) { - let (tokens, lexing_errors) = Lexer::lex(source_program); - let (module, mut parsing_errors) = program().parse_recovery_verbose(tokens); - - parsing_errors.extend(lexing_errors.into_iter().map(Into::into)); - let parsed_module = module.unwrap_or_default(); - - (parsed_module, parsing_errors) -} - -/// program: module EOF -fn program() -> impl NoirParser { - module().then_ignore(just(Token::EOF)) -} - -/// module: top_level_statement module -/// | %empty -pub fn module() -> impl NoirParser { - recursive(|module_parser| { - inner_doc_comments() - .then( - empty() - .to(ParsedModule::default()) - .then(spanned(top_level_statement(module_parser)).repeated()) - .foldl(|mut program, (statement, span)| { - if let Some(kind) = statement.kind.into_item_kind() { - program.items.push(Item { - kind, - span, - doc_comments: statement.doc_comments, - }); - } - program - }), - ) - .map(|(doc_comments, mut program)| { - program.inner_doc_comments = doc_comments; - program - }) - }) -} - -/// This parser is used for parsing top level statements in macros -pub fn top_level_items() -> impl NoirParser> { - top_level_statement(module()).repeated() -} - -pub fn top_level_statement<'a>( - module_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - outer_doc_comments() - .then(top_level_statement_kind(module_parser)) - .map(|(doc_comments, kind)| TopLevelStatement { kind, doc_comments }) -} - -/// top_level_statement: function_definition -/// | struct_definition -/// | trait_definition -/// | implementation -/// | submodule -/// | module_declaration -/// | use_statement -/// | global_declaration -fn top_level_statement_kind<'a>( - module_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - choice(( - function::function_definition(false).map(TopLevelStatementKind::Function), - structs::struct_definition(), - traits::trait_definition(), - traits::trait_implementation(), - implementation(), - type_alias_definition().then_ignore(force(just(Token::Semicolon))), - submodule(module_parser.clone()), - contract(module_parser), - module_declaration().then_ignore(force(just(Token::Semicolon))), - use_statement().then_ignore(force(just(Token::Semicolon))), - global_declaration().then_ignore(force(just(Token::Semicolon))), - inner_attribute().map(TopLevelStatementKind::InnerAttribute), - )) - .recover_via(top_level_statement_recovery()) -} - -/// Parses a non-trait implementation, adding a set of methods to a type. -/// -/// implementation: 'impl' generics type '{' function_definition ... '}' -fn implementation() -> impl NoirParser { - let method = spanned(function::function_definition(true)); - let methods = outer_doc_comments() - .then(method) - .map(|(doc_comments, (method, span))| (Documented::new(method, doc_comments), span)) - .repeated(); - - let methods_or_error = just(Token::LeftBrace) - .ignore_then(methods) - .then_ignore(just(Token::RightBrace)) - .or_not() - .validate(|methods, span, emit| { - if let Some(methods) = methods { - methods - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterImplType, - span, - )); - vec![] - } - }); - - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(parse_type().map_with_span(|typ, span| (typ, span))) - .then(where_clause()) - .then(methods_or_error) - .map(|args| { - let ((other_args, where_clause), methods) = args; - let (generics, (object_type, type_span)) = other_args; - TopLevelStatementKind::Impl(TypeImpl { - generics, - object_type, - type_span, - where_clause, - methods, - }) - }) -} - -/// global_declaration: 'global' ident global_type_annotation '=' literal -fn global_declaration() -> impl NoirParser { - let p = attributes::attributes() - .then(item_visibility()) - .then(maybe_comp_time()) - .then(spanned(keyword(Keyword::Mut)).or_not()) - .then_ignore(keyword(Keyword::Global).labelled(ParsingRuleLabel::Global)) - .then(ident().map(Pattern::Identifier)); - - let p = then_commit(p, optional_type_annotation()); - let p = then_commit_ignore(p, just(Token::Assign)); - let p = then_commit(p, expression()); - p.validate( - |((((((attributes, visibility), comptime), mutable), mut pattern), r#type), expression), - span, - emit| { - let global_attributes = - attributes::validate_secondary_attributes(attributes, span, emit); - - // Only comptime globals are allowed to be mutable, but we always parse the `mut` - // and throw the error in name resolution. - if let Some((_, mut_span)) = mutable { - let span = mut_span.merge(pattern.span()); - pattern = Pattern::Mutable(Box::new(pattern), span, false); - } - ( - LetStatement { - pattern, - r#type, - comptime, - expression, - attributes: global_attributes, - }, - visibility, - ) - }, - ) - .map(|(let_statement, visibility)| TopLevelStatementKind::Global(let_statement, visibility)) -} - -/// submodule: 'mod' ident '{' module '}' -fn submodule( - module_parser: impl NoirParser, -) -> impl NoirParser { - attributes() - .then(item_visibility()) - .then_ignore(keyword(Keyword::Mod)) - .then(ident()) - .then_ignore(just(Token::LeftBrace)) - .then(module_parser) - .then_ignore(just(Token::RightBrace)) - .validate(|(((attributes, visibility), name), contents), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::SubModule(ParsedSubModule { - visibility, - name, - contents, - outer_attributes: attributes, - is_contract: false, - }) - }) -} - -/// contract: 'contract' ident '{' module '}' -fn contract( - module_parser: impl NoirParser, -) -> impl NoirParser { - attributes() - .then(item_visibility()) - .then_ignore(keyword(Keyword::Contract)) - .then(ident()) - .then_ignore(just(Token::LeftBrace)) - .then(module_parser) - .then_ignore(just(Token::RightBrace)) - .validate(|(((attributes, visibility), name), contents), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::SubModule(ParsedSubModule { - visibility, - name, - contents, - outer_attributes: attributes, - is_contract: true, - }) - }) -} - -fn type_alias_definition() -> impl NoirParser { - use self::Keyword::Type; - - item_visibility() - .then_ignore(keyword(Type)) - .then(ident()) - .then(function::generics()) - .then_ignore(just(Token::Assign)) - .then(parse_type()) - .map_with_span(|(((visibility, name), generics), typ), span| { - TopLevelStatementKind::TypeAlias(NoirTypeAlias { - name, - generics, - typ, - visibility, - span, - }) - }) -} - -fn self_parameter() -> impl NoirParser { - let mut_ref_pattern = just(Token::Ampersand).then_ignore(keyword(Keyword::Mut)); - let mut_pattern = keyword(Keyword::Mut); - - mut_ref_pattern - .or(mut_pattern) - .map_with_span(|token, span| (token, span)) - .or_not() - .then(filter_map(move |span, found: Token| match found { - Token::Ident(ref word) if word == "self" => Ok(span), - _ => Err(ParserError::expected_label(ParsingRuleLabel::Parameter, found, span)), - })) - .map(|(pattern_keyword, ident_span)| { - let ident = Ident::new("self".to_string(), ident_span); - let path = Path::from_single("Self".to_owned(), ident_span); - let no_args = GenericTypeArgs::default(); - let mut self_type = - UnresolvedTypeData::Named(path, no_args, true).with_span(ident_span); - let mut pattern = Pattern::Identifier(ident); - - match pattern_keyword { - Some((Token::Ampersand, _)) => { - self_type = UnresolvedTypeData::MutableReference(Box::new(self_type)) - .with_span(ident_span); - } - Some((Token::Keyword(_), span)) => { - pattern = Pattern::Mutable(Box::new(pattern), span.merge(ident_span), true); - } - _ => (), - } - - Param { span: pattern.span(), pattern, typ: self_type, visibility: Visibility::Private } - }) -} - -/// Function declaration parameters differ from other parameters in that parameter -/// patterns are not allowed in declarations. All parameters must be identifiers. -fn function_declaration_parameters() -> impl NoirParser> { - let typ = parse_type().recover_via(parameter_recovery()); - let typ = just(Token::Colon).ignore_then(typ); - - let full_parameter = ident().recover_via(parameter_name_recovery()).then(typ); - let self_parameter = self_parameter().validate(|param, span, emit| { - match param.pattern { - Pattern::Identifier(ident) => (ident, param.typ), - other => { - emit(ParserError::with_reason( - ParserErrorReason::PatternInTraitFunctionParameter, - span, - )); - // into_ident panics on tuple or struct patterns but should be fine to call here - // since the `self` parser can only parse `self`, `mut self` or `&mut self`. - (other.into_ident(), param.typ) + let lexer = Lexer::new(source_program); + let mut parser = Parser::for_lexer(lexer); + let program = parser.parse_program(); + let errors = parser.errors; + (program, errors) +} + +enum TokenStream<'a> { + Lexer(Lexer<'a>), + Tokens(Tokens), +} + +impl<'a> TokenStream<'a> { + fn next(&mut self) -> Option { + match self { + TokenStream::Lexer(lexer) => lexer.next(), + TokenStream::Tokens(tokens) => { + // NOTE: `TokenStream::Tokens` is only created via `Parser::for_tokens(tokens)` which + // reverses `tokens`. That's why using `pop` here is fine (done for performance reasons). + tokens.0.pop().map(Ok) } } - }); - - let parameter = full_parameter.or(self_parameter); - - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} - -fn block_expr<'a>( - statement: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - block(statement).map(ExpressionKind::Block).map_with_span(Expression::new) -} - -pub fn block<'a>( - statement: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - use Token::*; - - statement - .recover_via(statement_recovery()) - .then(just(Semicolon).or_not().map_with_span(|s, span| (s, span))) - .map_with_span(|(kind, rest), span| (Statement { kind, span }, rest)) - .repeated() - .validate(check_statements_require_semicolon) - .delimited_by(just(LeftBrace), just(RightBrace)) - .recover_with(nested_delimiters( - LeftBrace, - RightBrace, - [(LeftParen, RightParen), (LeftBracket, RightBracket)], - |span| vec![Statement { kind: StatementKind::Error, span }], - )) - .map(|statements| BlockExpression { statements }) + } } -fn check_statements_require_semicolon( - statements: Vec<(Statement, (Option, Span))>, - _span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Vec { - let last = statements.len().saturating_sub(1); - let iter = statements.into_iter().enumerate(); - vecmap(iter, |(i, (statement, (semicolon, span)))| { - statement.add_semicolon(semicolon, span, i == last, emit) - }) -} +pub struct Parser<'a> { + pub(crate) errors: Vec, + tokens: TokenStream<'a>, -/// Parse an optional ': type' -fn optional_type_annotation<'a>() -> impl NoirParser + 'a { - ignore_then_commit(just(Token::Colon), parse_type()).or_not().map_with_span(|r#type, span| { - r#type.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span)) - }) + // We always have one look-ahead token for these cases: + // - check if we get `&` or `&mut` + // - check if we get `>` or `>>` + token: SpannedToken, + next_token: SpannedToken, + current_token_span: Span, + previous_token_span: Span, } -fn module_declaration() -> impl NoirParser { - attributes().then(item_visibility()).then_ignore(keyword(Keyword::Mod)).then(ident()).validate( - |((attributes, visibility), ident), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Module(ModuleDeclaration { - visibility, - ident, - outer_attributes: attributes, - }) - }, - ) -} +impl<'a> Parser<'a> { + pub fn for_lexer(lexer: Lexer<'a>) -> Self { + Self::new(TokenStream::Lexer(lexer)) + } -fn use_statement() -> impl NoirParser { - item_visibility() - .then_ignore(keyword(Keyword::Use)) - .then(use_tree()) - .map(|(visibility, use_tree)| TopLevelStatementKind::Import(use_tree, visibility)) -} + pub fn for_tokens(mut tokens: Tokens) -> Self { + tokens.0.reverse(); + Self::new(TokenStream::Tokens(tokens)) + } -fn rename() -> impl NoirParser> { - ignore_then_commit(keyword(Keyword::As), ident()).or_not() -} + pub fn for_str(str: &'a str) -> Self { + Self::for_lexer(Lexer::new(str)) + } -fn use_tree() -> impl NoirParser { - recursive(|use_tree| { - let simple = path::path_no_turbofish().then(rename()).map(|(mut prefix, alias)| { - let ident = prefix.pop().ident; - UseTree { prefix, kind: UseTreeKind::Path(ident, alias) } - }); - - let list = { - let prefix = maybe_empty_path().then_ignore(just(Token::DoubleColon)); - let tree = use_tree - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)) - .map(UseTreeKind::List); - - prefix.then(tree).map(|(prefix, kind)| UseTree { prefix, kind }) + fn new(tokens: TokenStream<'a>) -> Self { + let mut parser = Self { + errors: Vec::new(), + tokens, + token: eof_spanned_token(), + next_token: eof_spanned_token(), + current_token_span: Default::default(), + previous_token_span: Default::default(), }; + parser.read_two_first_tokens(); + parser + } - choice((list, simple)) - }) -} - -fn statement<'a, P, P2>( - expr_parser: P, - expr_no_constructors: P2, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, -{ - recursive(|statement| { - choice(( - assertion::constrain(expr_parser.clone()), - assertion::assertion(expr_parser.clone()), - declaration(expr_parser.clone()), - assignment(expr_parser.clone()), - if_statement(expr_no_constructors.clone(), statement.clone()), - block_statement(statement.clone()), - for_loop(expr_no_constructors.clone(), statement.clone()), - break_statement(), - continue_statement(), - return_statement(expr_parser.clone()), - comptime_statement(expr_parser.clone(), expr_no_constructors, statement), - interned_statement(), - expr_parser.map(StatementKind::Expression), - )) - }) -} - -pub fn fresh_statement() -> impl NoirParser { - statement(expression(), expression_no_constructors(expression())) -} - -fn break_statement() -> impl NoirParser { - keyword(Keyword::Break).to(StatementKind::Break) -} - -fn continue_statement() -> impl NoirParser { - keyword(Keyword::Continue).to(StatementKind::Continue) -} - -fn comptime_statement<'a, P1, P2, S>( - expr: P1, - expr_no_constructors: P2, - statement: S, -) -> impl NoirParser + 'a -where - P1: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - let comptime_statement = choice(( - declaration(expr), - for_loop(expr_no_constructors, statement.clone()), - block(statement).map_with_span(|block, span| { - StatementKind::Expression(Expression::new(ExpressionKind::Block(block), span)) - }), - )) - .map_with_span(|kind, span| Box::new(Statement { kind, span })); - - keyword(Keyword::Comptime).ignore_then(comptime_statement).map(StatementKind::Comptime) -} - -/// Comptime in an expression position only accepts entire blocks -fn comptime_expr<'a, S>(statement: S) -> impl NoirParser + 'a -where - S: NoirParser + 'a, -{ - keyword(Keyword::Comptime) - .ignore_then(spanned(block(statement))) - .map(|(block, span)| ExpressionKind::Comptime(block, span)) -} + /// Program = Module + pub(crate) fn parse_program(&mut self) -> ParsedModule { + self.parse_module( + false, // nested + ) + } -fn unsafe_expr<'a, S>(statement: S) -> impl NoirParser + 'a -where - S: NoirParser + 'a, -{ - keyword(Keyword::Unsafe) - .ignore_then(spanned(block(statement))) - .map(|(block, span)| ExpressionKind::Unsafe(block, span)) -} + /// Module = InnerDocComments Item* + pub(crate) fn parse_module(&mut self, nested: bool) -> ParsedModule { + let inner_doc_comments = self.parse_inner_doc_comments(); + let items = self.parse_module_items(nested); -fn let_statement<'a, P>( - expr_parser: P, -) -> impl NoirParser<((Pattern, UnresolvedType), Expression)> + 'a -where - P: ExprParser + 'a, -{ - let p = - ignore_then_commit(keyword(Keyword::Let).labelled(ParsingRuleLabel::Statement), pattern()); - let p = p.then(optional_type_annotation()); - let p = then_commit_ignore(p, just(Token::Assign)); - then_commit(p, expr_parser) -} + ParsedModule { items, inner_doc_comments } + } -fn declaration<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - attributes().then(let_statement(expr_parser)).validate( - |(attributes, ((pattern, typ), expr)), span, emit| { - let attributes = attributes::validate_secondary_attributes(attributes, span, emit); - StatementKind::new_let(pattern, typ, expr, attributes) - }, - ) -} + /// Parses an LValue. If an LValue can't be parsed, an error is recorded and a default LValue is returned. + pub(crate) fn parse_lvalue_or_error(&mut self) -> LValue { + let start_span = self.current_token_span; -pub fn pattern() -> impl NoirParser { - recursive(|pattern| { - let ident_pattern = ident().map(Pattern::Identifier).map_err(|mut error| { - if matches!(error.found(), Token::IntType(..)) { - error = ParserError::with_reason( - ParserErrorReason::ExpectedPatternButFoundType(error.found().clone()), - error.span(), - ); + if let Some(token) = self.eat_kind(TokenKind::InternedLValue) { + match token.into_token() { + Token::InternedLValue(lvalue) => { + return LValue::Interned(lvalue, self.span_since(start_span)); + } + _ => unreachable!(), } + } - error - }); - - let mut_pattern = keyword(Keyword::Mut) - .ignore_then(pattern.clone()) - .map_with_span(|inner, span| Pattern::Mutable(Box::new(inner), span, false)); - - let short_field = ident().map(|name| (name.clone(), Pattern::Identifier(name))); - let long_field = ident().then_ignore(just(Token::Colon)).then(pattern.clone()); - - let struct_pattern_fields = long_field - .or(short_field) - .separated_by(just(Token::Comma)) - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - - let struct_pattern = path(super::parse_type()) - .then(struct_pattern_fields) - .map_with_span(|(typename, fields), span| Pattern::Struct(typename, fields, span)); - - let tuple_pattern = pattern - .separated_by(just(Token::Comma)) - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(Pattern::Tuple); - - let interned = - token_kind(TokenKind::InternedPattern).map_with_span(|token, span| match token { - Token::InternedPattern(id) => Pattern::Interned(id, span), - _ => unreachable!( - "token_kind(InternedPattern) guarantees we parse an interned pattern" - ), - }); - - choice((mut_pattern, tuple_pattern, struct_pattern, ident_pattern, interned)) - }) - .labelled(ParsingRuleLabel::Pattern) -} - -fn assignment<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let fallible = - lvalue(expr_parser.clone()).then(assign_operator()).labelled(ParsingRuleLabel::Statement); - - then_commit(fallible, expr_parser).map_with_span( - |((identifier, operator), expression), span| { - StatementKind::assign(identifier, operator, expression, span) - }, - ) -} - -/// Parse an assignment operator `=` optionally prefixed by a binary operator for a combined -/// assign statement shorthand. Notably, this must handle a few corner cases with how `>>` is -/// lexed as two separate greater-than operators rather than a single right-shift. -fn assign_operator() -> impl NoirParser { - let shorthand_operators = Token::assign_shorthand_operators(); - // We need to explicitly check for right_shift here since it is actually - // two separate greater-than operators. - let shorthand_operators = right_shift_operator().or(one_of(shorthand_operators)); - let shorthand_syntax = shorthand_operators.then_ignore(just(Token::Assign)); - - // Since >> is lexed as two separate "greater-than"s, >>= is lexed as > >=, so - // we need to account for that case here as well. - let right_shift_fix = - just(Token::Greater).then(just(Token::GreaterEqual)).to(Token::ShiftRight); - - let shorthand_syntax = shorthand_syntax.or(right_shift_fix); - just(Token::Assign).or(shorthand_syntax) -} - -enum LValueRhs { - MemberAccess(Ident, Span), - Index(Expression, Span), -} - -pub fn lvalue<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - recursive(|lvalue| { - let l_ident = ident().map(LValue::Ident); - - let dereferences = just(Token::Star) - .ignore_then(lvalue.clone()) - .map_with_span(|lvalue, span| LValue::Dereference(Box::new(lvalue), span)); - - let parenthesized = lvalue.delimited_by(just(Token::LeftParen), just(Token::RightParen)); + let expr = self.parse_expression_or_error(); + if let Some(lvalue) = LValue::from_expression(expr) { + lvalue + } else { + self.expected_label(ParsingRuleLabel::LValue); + LValue::Ident(Ident::default()) + } + } - let interned = - token_kind(TokenKind::InternedLValue).map_with_span(|token, span| match token { - Token::InternedLValue(id) => LValue::Interned(id, span), - _ => unreachable!( - "token_kind(InternedLValue) guarantees we parse an interned lvalue" - ), - }); + /// Invokes `parsing_function` (`parsing_function` must be some `parse_*` method of the parser) + /// and returns the result if the parser has no errors, and if the parser consumed all tokens. + /// Otherwise returns the list of errors. + pub fn parse_result(mut self, parsing_function: F) -> Result> + where + F: FnOnce(&mut Parser<'a>) -> T, + { + let item = parsing_function(&mut self); + if !self.at_eof() { + self.expected_token(Token::EOF); + return Err(self.errors); + } - let term = choice((parenthesized, dereferences, l_ident, interned)); + if self.errors.is_empty() { + Ok(item) + } else { + Err(self.errors) + } + } - let l_member_rhs = - just(Token::Dot).ignore_then(field_name()).map_with_span(LValueRhs::MemberAccess); + /// Invokes `parsing_function` (`parsing_function` must be some `parse_*` method of the parser) + /// and returns the result if the parser has no errors, and if the parser consumed all tokens. + /// Otherwise returns None. + pub fn parse_option(self, parsing_function: F) -> Option + where + F: FnOnce(&mut Parser<'a>) -> Option, + { + match self.parse_result(parsing_function) { + Ok(item) => item, + Err(_) => None, + } + } - let l_index = expr_parser - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map_with_span(LValueRhs::Index); + /// Bumps this parser by one token. Returns the token that was previously the "current" token. + fn bump(&mut self) -> SpannedToken { + self.previous_token_span = self.current_token_span; + let next_next_token = self.read_token_internal(); + let next_token = std::mem::replace(&mut self.next_token, next_next_token); + let token = std::mem::replace(&mut self.token, next_token); + self.current_token_span = self.token.to_span(); + token + } - term.then(l_member_rhs.or(l_index).repeated()).foldl(|lvalue, rhs| match rhs { - LValueRhs::MemberAccess(field_name, span) => { - let span = lvalue.span().merge(span); - LValue::MemberAccess { object: Box::new(lvalue), field_name, span } - } - LValueRhs::Index(index, span) => { - let span = lvalue.span().merge(span); - LValue::Index { array: Box::new(lvalue), index, span } - } - }) - }) -} + fn read_two_first_tokens(&mut self) { + self.token = self.read_token_internal(); + self.current_token_span = self.token.to_span(); + self.next_token = self.read_token_internal(); + } -fn call_data() -> impl NoirParser { - keyword(Keyword::CallData).then(parenthesized(literal())).validate(|token, span, emit| { - match token { - (_, ExpressionKind::Literal(Literal::Integer(x, _))) => { - let id = x.to_u128() as u32; - Visibility::CallData(id) - } - _ => { - emit(ParserError::with_reason(ParserErrorReason::InvalidCallDataIdentifier, span)); - Visibility::CallData(0) + fn read_token_internal(&mut self) -> SpannedToken { + loop { + let token = self.tokens.next(); + if let Some(token) = token { + match token { + Ok(token) => return token, + Err(lexer_error) => self.errors.push(lexer_error.into()), + } + } else { + return eof_spanned_token(); } } - }) -} - -pub fn expression() -> impl ExprParser { - recursive(|expr| { - expression_with_precedence( - Precedence::Lowest, - expr.clone(), - expression_no_constructors(expr.clone()), - statement(expr.clone(), expression_no_constructors(expr)), - false, - true, - ) - }) - .labelled(ParsingRuleLabel::Expression) -} - -fn expression_no_constructors<'a, P>(expr_parser: P) -> impl ExprParser + 'a -where - P: ExprParser + 'a, -{ - recursive(|expr_no_constructors| { - expression_with_precedence( - Precedence::Lowest, - expr_parser.clone(), - expr_no_constructors.clone(), - statement(expr_parser, expr_no_constructors), - false, - false, - ) - }) - .labelled(ParsingRuleLabel::Expression) -} - -fn return_statement<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - ignore_then_commit(keyword(Keyword::Return), expr_parser.or_not()) - .validate(|_, span, emit| { - emit(ParserError::with_reason(ParserErrorReason::EarlyReturn, span)); - StatementKind::Error - }) - .labelled(ParsingRuleLabel::Statement) -} + } -// An expression is a single term followed by 0 or more (OP subexpression)* -// where OP is an operator at the given precedence level and subexpression -// is an expression at the current precedence level plus one. -fn expression_with_precedence<'a, P, P2, S>( - precedence: Precedence, - expr_parser: P, - expr_no_constructors: P2, - statement: S, - // True if we should only parse the restricted subset of operators valid within type expressions - is_type_expression: bool, - // True if we should also parse constructors `Foo { field1: value1, ... }` as an expression. - // This is disabled when parsing the condition of an if statement due to a parsing conflict - // with `then` bodies containing only a single variable. - allow_constructors: bool, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - if precedence == Precedence::Highest { - if is_type_expression { - type_expression_term(expr_parser).boxed().labelled(ParsingRuleLabel::Term) + fn eat_kind(&mut self, kind: TokenKind) -> Option { + if self.token.kind() == kind { + Some(self.bump()) } else { - term(expr_parser, expr_no_constructors, statement, allow_constructors) - .boxed() - .labelled(ParsingRuleLabel::Term) + None } - } else { - let next_precedence = - if is_type_expression { precedence.next_type_precedence() } else { precedence.next() }; - - let next_expr = expression_with_precedence( - next_precedence, - expr_parser, - expr_no_constructors, - statement, - is_type_expression, - allow_constructors, - ); - - next_expr - .clone() - .then(then_commit(operator_with_precedence(precedence), next_expr).repeated()) - .foldl(create_infix_expression) - .boxed() - .labelled(ParsingRuleLabel::Expression) } -} -fn create_infix_expression(lhs: Expression, (operator, rhs): (BinaryOp, Expression)) -> Expression { - let span = lhs.span.merge(rhs.span); - let infix = Box::new(InfixExpression { lhs, operator, rhs }); - - Expression { span, kind: ExpressionKind::Infix(infix) } -} - -fn operator_with_precedence(precedence: Precedence) -> impl NoirParser> { - right_shift_operator() - .or(any()) // Parse any single token, we're validating it as an operator next - .try_map(move |token, span| { - if Precedence::token_precedence(&token) == Some(precedence) { - Ok(token.try_into_binary_op(span).unwrap()) + fn eat_keyword(&mut self, keyword: Keyword) -> bool { + if let Token::Keyword(kw) = self.token.token() { + if *kw == keyword { + self.bump(); + true } else { - Err(ParserError::expected_label(ParsingRuleLabel::BinaryOperator, token, span)) + false } - }) -} - -fn term<'a, P, P2, S>( - expr_parser: P, - expr_no_constructors: P2, - statement: S, - allow_constructors: bool, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - recursive(move |term_parser| { - choice(( - not(term_parser.clone()), - negation(term_parser.clone()), - mutable_reference(term_parser.clone()), - dereference(term_parser), - )) - .map_with_span(Expression::new) - // right-unary operators like a[0] or a.f bind more tightly than left-unary - // operators like - or !, so that !a[0] is parsed as !(a[0]). This is a bit - // awkward for casts so -a as i32 actually binds as -(a as i32). - .or(atom_or_right_unary( - expr_parser, - expr_no_constructors, - statement, - allow_constructors, - parse_type(), - )) - }) -} - -/// The equivalent of a 'term' for use in type expressions. Unlike regular terms, the grammar here -/// is restricted to no longer include right-unary expressions, unary not, and most atoms. -fn type_expression_term<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - recursive(move |term_parser| { - negation(term_parser).map_with_span(Expression::new).or(type_expression_atom(expr_parser)) - }) -} - -fn atom_or_right_unary<'a, P, P2, S>( - expr_parser: P, - expr_no_constructors: P2, - statement: S, - allow_constructors: bool, - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - enum UnaryRhs { - Call((Option, Vec)), - ArrayIndex(Expression), - Cast(UnresolvedType), - MemberAccess(UnaryRhsMemberAccess), - /// This is to allow `foo.` (no identifier afterwards) to be parsed as `foo` - /// and produce an error, rather than just erroring (for LSP). - JustADot, + } else { + false + } } - // `(arg1, ..., argN)` in `my_func(arg1, ..., argN)` - // Optionally accepts a leading `!` for macro calls. - let call_rhs = just(Token::Bang) - .or_not() - .then(parenthesized(expression_list(expr_parser.clone()))) - .map(UnaryRhs::Call); - - // `[expr]` in `arr[expr]` - let array_rhs = expr_parser - .clone() - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(UnaryRhs::ArrayIndex); - - // `as Type` in `atom as Type` - let cast_rhs = keyword(Keyword::As) - .ignore_then(type_parser.clone()) - .map(UnaryRhs::Cast) - .labelled(ParsingRuleLabel::Cast); - - // A turbofish operator is optional in a method call to specify generic types - let turbofish = primitives::turbofish(type_parser); - - // `::!(arg1, .., argN)` with the turbofish and macro portions being optional. - let method_call_rhs = turbofish - .then(just(Token::Bang).or_not()) - .then(parenthesized(expression_list(expr_parser.clone()))) - .validate(|((turbofish, macro_call), args), span, emit| { - if turbofish.as_ref().map_or(false, |generics| !generics.named_args.is_empty()) { - let reason = ParserErrorReason::AssociatedTypesNotAllowedInMethodCalls; - emit(ParserError::with_reason(reason, span)); + fn eat_ident(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::Ident) { + match token.into_token() { + Token::Ident(ident) => Some(Ident::new(ident, self.previous_token_span)), + _ => unreachable!(), } + } else { + None + } + } - let macro_call = macro_call.is_some(); - let turbofish = turbofish.map(|generics| generics.ordered_args); - UnaryRhsMethodCall { turbofish, macro_call, args } - }); - - // `.foo` or `.foo(args)` in `atom.foo` or `atom.foo(args)` - let member_rhs = just(Token::Dot) - .ignore_then(field_name()) - .then(method_call_rhs.or_not()) - .map(|(method_or_field, method_call)| { - UnaryRhs::MemberAccess(UnaryRhsMemberAccess { method_or_field, method_call }) - }) - .labelled(ParsingRuleLabel::FieldAccess); - - let just_a_dot = - just(Token::Dot).map(|_| UnaryRhs::JustADot).validate(|value, span, emit_error| { - emit_error(ParserError::with_reason( - ParserErrorReason::ExpectedIdentifierAfterDot, - span, - )); - value - }); - - let rhs = choice((call_rhs, array_rhs, cast_rhs, member_rhs, just_a_dot)); - - foldl_with_span( - atom(expr_parser, expr_no_constructors, statement, allow_constructors), - rhs, - |lhs, rhs, span| match rhs { - UnaryRhs::Call((is_macro, args)) => { - Expression::call(lhs, is_macro.is_some(), args, span) - } - UnaryRhs::ArrayIndex(index) => Expression::index(lhs, index, span), - UnaryRhs::Cast(r#type) => Expression::cast(lhs, r#type, span), - UnaryRhs::MemberAccess(field) => { - Expression::member_access_or_method_call(lhs, field, span) + fn eat_self(&mut self) -> bool { + if let Token::Ident(ident) = self.token.token() { + if ident == "self" { + self.bump(); + return true; } - UnaryRhs::JustADot => lhs, - }, - ) -} - -fn if_expr<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - S: NoirParser + 'a, -{ - recursive(|if_parser| { - let if_block = block_expr(statement.clone()); - // The else block could also be an `else if` block, in which case we must recursively parse it. - let else_block = block_expr(statement).or(if_parser.map_with_span(|kind, span| { - // Wrap the inner `if` expression in a block expression. - // i.e. rewrite the sugared form `if cond1 {} else if cond2 {}` as `if cond1 {} else { if cond2 {} }`. - let if_expression = Expression::new(kind, span); - let desugared_else = BlockExpression { - statements: vec![Statement { - kind: StatementKind::Expression(if_expression), - span, - }], - }; - Expression::new(ExpressionKind::Block(desugared_else), span) - })); - - keyword(Keyword::If) - .ignore_then(expr_no_constructors) - .then(if_block.then(keyword(Keyword::Else).ignore_then(else_block).or_not()).or_not()) - .validate(|(condition, consequence_and_alternative), span, emit_error| { - if let Some((consequence, alternative)) = consequence_and_alternative { - ExpressionKind::If(Box::new(IfExpression { - condition, - consequence, - alternative, - })) - } else { - // We allow `if cond` without a block mainly for LSP, so that it parses right - // and autocompletion works there. - emit_error(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBraceAfterIfCondition, - span, - )); - - let span_end = condition.span.end(); - ExpressionKind::If(Box::new(IfExpression { - condition, - consequence: Expression::new( - ExpressionKind::Error, - Span::from(span_end..span_end), - ), - alternative: None, - })) - } - }) - }) -} - -fn if_statement<'a, P, S>( - expr_no_constructors: P, - statement: S, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - S: NoirParser + 'a, -{ - if_expr(expr_no_constructors, statement).map_with_span(|expression_kind, span| { - StatementKind::Expression(Expression::new(expression_kind, span)) - }) -} - -fn block_statement<'a, S>(statement: S) -> impl NoirParser + 'a -where - S: NoirParser + 'a, -{ - block(statement).map_with_span(|block, span| { - StatementKind::Expression(Expression::new(ExpressionKind::Block(block), span)) - }) -} + } -fn for_loop<'a, P, S>(expr_no_constructors: P, statement: S) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - S: NoirParser + 'a, -{ - keyword(Keyword::For) - .ignore_then(ident()) - .then_ignore(keyword(Keyword::In)) - .then(for_range(expr_no_constructors)) - .then(block_expr(statement)) - .map_with_span(|((identifier, range), block), span| { - StatementKind::For(ForLoopStatement { identifier, range, block, span }) - }) -} + false + } -/// The 'range' of a for loop. Either an actual range `start .. end` or an array expression. -fn for_range

(expr_no_constructors: P) -> impl NoirParser -where - P: ExprParser, -{ - expr_no_constructors - .clone() - .then(just(Token::DoubleDot).or(just(Token::DoubleDotEqual))) - .then(expr_no_constructors.clone()) - .map(|((start, dots), end)| { - if dots == Token::DoubleDotEqual { - ForRange::range_inclusive(start, end) - } else { - ForRange::range(start, end) + fn eat_int_type(&mut self) -> Option { + let is_int_type = matches!(self.token.token(), Token::IntType(..)); + if is_int_type { + let token = self.bump(); + match token.into_token() { + Token::IntType(int_type) => Some(int_type), + _ => unreachable!(), } - }) - .or(expr_no_constructors.map(ForRange::Array)) -} - -fn array_expr

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - standard_array(expr_parser.clone()).or(array_sugar(expr_parser)) -} - -/// [a, b, c, ...] -fn standard_array

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expression_list(expr_parser) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .validate(|elements, _span, _emit| ExpressionKind::array(elements)) -} - -/// [a; N] -fn array_sugar

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expr_parser - .clone() - .then(just(Token::Semicolon).ignore_then(expr_parser)) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(|(lhs, count)| ExpressionKind::repeated_array(lhs, count)) -} - -fn slice_expr

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Ampersand) - .ignore_then(standard_slice(expr_parser.clone()).or(slice_sugar(expr_parser))) -} - -/// &[a, b, c, ...] -fn standard_slice

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expression_list(expr_parser) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .validate(|elements, _span, _emit| ExpressionKind::slice(elements)) -} - -/// &[a; N] -fn slice_sugar

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - expr_parser - .clone() - .then(just(Token::Semicolon).ignore_then(expr_parser)) - .delimited_by(just(Token::LeftBracket), just(Token::RightBracket)) - .map(|(lhs, count)| ExpressionKind::repeated_slice(lhs, count)) -} - -fn expression_list

(expr_parser: P) -> impl NoirParser> -where - P: ExprParser, -{ - expr_parser.separated_by(just(Token::Comma)).allow_trailing() -} - -/// Atoms are parameterized on whether constructor expressions are allowed or not. -/// Certain constructs like `if` and `for` disallow constructor expressions when a -/// block may be expected. -fn atom<'a, P, P2, S>( - expr_parser: P, - expr_no_constructors: P2, - statement: S, - allow_constructors: bool, -) -> impl NoirParser + 'a -where - P: ExprParser + 'a, - P2: ExprParser + 'a, - S: NoirParser + 'a, -{ - choice(( - if_expr(expr_no_constructors, statement.clone()), - slice_expr(expr_parser.clone()), - array_expr(expr_parser.clone()), - if allow_constructors { - constructor(expr_parser.clone()).boxed() } else { - nothing().boxed() - }, - lambdas::lambda(expr_parser.clone()), - block(statement.clone()).map(ExpressionKind::Block), - comptime_expr(statement.clone()), - unsafe_expr(statement), - quote(), - unquote(expr_parser.clone()), - variable(), - literal(), - as_trait_path(parse_type()).map(ExpressionKind::AsTraitPath), - type_path(parse_type()), - macro_quote_marker(), - interned_expr(), - interned_statement_expr(), - )) - .map_with_span(Expression::new) - .or(parenthesized(expr_parser.clone()).map_with_span(|sub_expr, span| { - Expression::new(ExpressionKind::Parenthesized(sub_expr.into()), span) - })) - .or(tuple(expr_parser)) - .labelled(ParsingRuleLabel::Atom) -} - -/// Atoms within type expressions are limited to only variables, literals, and parenthesized -/// type expressions. -fn type_expression_atom<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - primitives::variable_no_turbofish() - .or(literal()) - .map_with_span(Expression::new) - .or(parenthesized(expr_parser)) - .labelled(ParsingRuleLabel::Atom) -} - -fn quote() -> impl NoirParser { - token_kind(TokenKind::Quote).map(|token| { - ExpressionKind::Quote(match token { - Token::Quote(tokens) => tokens, - _ => unreachable!("token_kind(Quote) should guarantee parsing only a quote token"), - }) - }) -} - -/// unquote: '$' variable -/// | '$' '(' expression ')' -fn unquote<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let unquote = variable().map_with_span(Expression::new).or(parenthesized(expr_parser)); - just(Token::DollarSign).ignore_then(unquote).map(|expr| ExpressionKind::Unquote(Box::new(expr))) -} + None + } + } -fn tuple

(expr_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - parenthesized(expression_list(expr_parser)).map_with_span(|elements, span| { - let kind = if elements.is_empty() { - ExpressionKind::Literal(Literal::Unit) + fn eat_int(&mut self) -> Option { + if matches!(self.token.token(), Token::Int(..)) { + let token = self.bump(); + match token.into_token() { + Token::Int(int) => Some(int), + _ => unreachable!(), + } } else { - ExpressionKind::Tuple(elements) - }; - Expression::new(kind, span) - }) -} - -fn field_name() -> impl NoirParser { - ident().or(token_kind(TokenKind::Literal).validate(|token, span, emit| match token { - Token::Int(_) => Ident::from(Spanned::from(span, token.to_string())), - other => { - emit(ParserError::with_reason(ParserErrorReason::ExpectedFieldName(other), span)); - Ident::error(span) + None } - })) -} - -fn constructor(expr_parser: impl ExprParser) -> impl NoirParser { - let args = constructor_field(expr_parser) - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::LeftBrace), just(Token::RightBrace)); - - 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)> -where - P: ExprParser, -{ - let long_form = ident().then_ignore(just(Token::Colon)).then(expr_parser); - let short_form = ident().map(|ident| (ident.clone(), ident.into())); - long_form.or(short_form) -} - -#[cfg(test)] -mod test { - use super::test_helpers::*; - use super::*; - use crate::ast::ArrayLiteral; - - #[test] - fn parse_infix() { - let valid = vec!["x + 6", "x - k", "x + (x + a)", " x * (x + a) + (x - 4)"]; - parse_all(expression(), valid); - parse_all_failing(expression(), vec!["y ! x"]); } - #[test] - fn parse_function_call() { - let valid = vec![ - "std::hash ()", - " std::hash(x,y,a+b)", - "crate::foo (x)", - "hash (x,)", - "(foo + bar)()", - "(bar)()()()", - ]; - parse_all(expression(), valid); + fn eat_bool(&mut self) -> Option { + if matches!(self.token.token(), Token::Bool(..)) { + let token = self.bump(); + match token.into_token() { + Token::Bool(bool) => Some(bool), + _ => unreachable!(), + } + } else { + None + } } - #[test] - fn parse_cast() { - let expression_nc = expression_no_constructors(expression()); - parse_all( - atom_or_right_unary( - expression(), - expression_no_constructors(expression()), - fresh_statement(), - true, - parse_type(), - ), - vec!["x as u8", "x as u16", "0 as Field", "(x + 3) as [Field; 8]"], - ); - parse_all_failing( - atom_or_right_unary(expression(), expression_nc, fresh_statement(), true, parse_type()), - vec!["x as pub u8"], - ); + fn eat_str(&mut self) -> Option { + if matches!(self.token.token(), Token::Str(..)) { + let token = self.bump(); + match token.into_token() { + Token::Str(string) => Some(string), + _ => unreachable!(), + } + } else { + None + } } - #[test] - fn parse_array_index() { - let valid = vec![ - "x[9]", - "y[x+a]", - " foo [foo+5]", - "baz[bar]", - "foo.bar[3] as Field .baz as u32 [7]", - ]; - parse_all( - atom_or_right_unary( - expression(), - expression_no_constructors(expression()), - fresh_statement(), - true, - parse_type(), - ), - valid, - ); + fn eat_raw_str(&mut self) -> Option<(String, u8)> { + if matches!(self.token.token(), Token::RawStr(..)) { + let token = self.bump(); + match token.into_token() { + Token::RawStr(string, n) => Some((string, n)), + _ => unreachable!(), + } + } else { + None + } } - fn expr_to_array(expr: ExpressionKind) -> ArrayLiteral { - let lit = match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - }; - - match lit { - Literal::Array(arr) => arr, - _ => unreachable!("expected an array"), + fn eat_fmt_str(&mut self) -> Option { + if matches!(self.token.token(), Token::FmtStr(..)) { + let token = self.bump(); + match token.into_token() { + Token::FmtStr(string) => Some(string), + _ => unreachable!(), + } + } else { + None } } - #[test] - fn parse_array() { - let valid = vec![ - "[0, 1, 2,3, 4]", - "[0,1,2,3,4,]", // Trailing commas are valid syntax - "[0;5]", - ]; - - for expr in parse_all(array_expr(expression()), valid) { - match expr_to_array(expr) { - ArrayLiteral::Standard(elements) => assert_eq!(elements.len(), 5), - ArrayLiteral::Repeated { length, .. } => { - assert_eq!(length.kind, ExpressionKind::integer(5i128.into())); - } + fn eat_quote(&mut self) -> Option { + if matches!(self.token.token(), Token::Quote(..)) { + let token = self.bump(); + match token.into_token() { + Token::Quote(tokens) => Some(tokens), + _ => unreachable!(), } + } else { + None } - - parse_all_failing( - array_expr(expression()), - vec!["0,1,2,3,4]", "[[0,1,2,3,4]", "[0,1,2,,]", "[0,1,2,3,4"], - ); } - #[test] - fn parse_array_sugar() { - let valid = vec!["[0;7]", "[(1, 2); 4]", "[0;Four]", "[2;1+3-a]"]; - parse_all(array_expr(expression()), valid); - - let invalid = vec!["[0;;4]", "[1, 2; 3]"]; - parse_all_failing(array_expr(expression()), invalid); + fn eat_comma(&mut self) -> bool { + self.eat(Token::Comma) } - fn expr_to_slice(expr: ExpressionKind) -> ArrayLiteral { - let lit = match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - }; - - match lit { - Literal::Slice(arr) => arr, - _ => unreachable!("expected a slice: {:?}", lit), + fn eat_commas(&mut self) -> bool { + if self.eat_comma() { + while self.eat_comma() { + self.push_error(ParserErrorReason::UnexpectedComma, self.previous_token_span); + } + true + } else { + false } } - #[test] - fn parse_slice() { - let valid = vec![ - "&[0, 1, 2,3, 4]", - "&[0,1,2,3,4,]", // Trailing commas are valid syntax - "&[0;5]", - ]; - - for expr in parse_all(slice_expr(expression()), valid) { - match expr_to_slice(expr) { - ArrayLiteral::Standard(elements) => assert_eq!(elements.len(), 5), - ArrayLiteral::Repeated { length, .. } => { - assert_eq!(length.kind, ExpressionKind::integer(5i128.into())); - } + fn eat_semicolon(&mut self) -> bool { + self.eat(Token::Semicolon) + } + + fn eat_semicolons(&mut self) -> bool { + if self.eat_semicolon() { + while self.eat_semicolon() { + self.push_error(ParserErrorReason::UnexpectedSemicolon, self.previous_token_span); } + true + } else { + false } - - parse_all_failing( - slice_expr(expression()), - vec!["0,1,2,3,4]", "&[[0,1,2,3,4]", "&[0,1,2,,]", "&[0,1,2,3,4"], - ); } - #[test] - fn parse_slice_sugar() { - let valid = vec!["&[0;7]", "&[(1, 2); 4]", "&[0;Four]", "&[2;1+3-a]"]; - parse_all(slice_expr(expression()), valid); - - let invalid = vec!["&[0;;4]", "&[1, 2; 3]"]; - parse_all_failing(slice_expr(expression()), invalid); + fn eat_colon(&mut self) -> bool { + self.eat(Token::Colon) } - #[test] - fn parse_block() { - parse_with(block(fresh_statement()), "{ [0,1,2,3,4] }").unwrap(); - - parse_all_failing( - block(fresh_statement()), - vec![ - "[0,1,2,3,4] }", - "{ [0,1,2,3,4]", - "{ [0,1,2,,] }", // Contents of the block must still be a valid expression - "{ [0,1,2,3 }", - "{ 0,1,2,3] }", - "[[0,1,2,3,4]}", - ], - ); + fn eat_double_colon(&mut self) -> bool { + self.eat(Token::DoubleColon) } - #[test] - fn parse_let() { - // Why is it valid to specify a let declaration as having type u8? - // - // Let statements are not type checked here, so the parser will accept as - // long as it is a type. Other statements such as Public are type checked - // Because for now, they can only have one type - parse_all( - declaration(expression()), - vec!["let _ = 42", "let x = y", "let x : u8 = y", "let x: u16 = y"], - ); + fn eat_left_paren(&mut self) -> bool { + self.eat(Token::LeftParen) } - #[test] - fn parse_invalid_pub() { - // pub cannot be used to declare a statement - parse_all_failing(fresh_statement(), vec!["pub x = y", "pub x : pub Field = y"]); + fn eat_right_paren(&mut self) -> bool { + self.eat(Token::RightParen) } - #[test] - fn parse_for_loop() { - parse_all( - for_loop(expression_no_constructors(expression()), fresh_statement()), - vec![ - "for i in x+y..z {}", - "for i in 0..100 { foo; bar }", - "for i in 90..=100 { foo; bar }", - ], - ); - - parse_all_failing( - for_loop(expression_no_constructors(expression()), fresh_statement()), - vec![ - "for 1 in x+y..z {}", // Cannot have a literal as the loop identifier - "for i in 0...100 {}", // Only '..' is supported - "for i in .. {}", // Range needs needs bounds - "for i in ..=10 {}", // Range needs lower bound - "for i in 0.. {}", // Range needs upper bound - ], - ); + fn eat_left_brace(&mut self) -> bool { + self.eat(Token::LeftBrace) } - #[test] - fn parse_parenthesized_expression() { - parse_all( - atom(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["(0)", "(x+a)", "({(({{({(nested)})}}))})"], - ); - parse_all_failing( - atom(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["(x+a", "((x+a)", "(,)"], - ); + fn eat_left_bracket(&mut self) -> bool { + self.eat(Token::LeftBracket) } - #[test] - fn parse_tuple() { - parse_all(tuple(expression()), vec!["()", "(x,)", "(a,b+2)", "(a,(b,c,),d,)"]); + fn eat_right_bracket(&mut self) -> bool { + self.eat(Token::RightBracket) } - #[test] - fn parse_if_expr() { - parse_all( - if_expr(expression_no_constructors(expression()), fresh_statement()), - vec!["if x + a { } else { }", "if x {}", "if x {} else if y {} else {}"], - ); - - parse_all_failing( - if_expr(expression_no_constructors(expression()), fresh_statement()), - vec!["if (x / a) + 1 {} else", "if foo then 1 else 2", "if true { 1 }else 3"], - ); + fn eat_less(&mut self) -> bool { + self.eat(Token::Less) } - #[test] - fn parse_if_without_block() { - let src = "if foo"; - let parser = if_expr(expression_no_constructors(expression()), fresh_statement()); - let (expression_kind, errors) = parse_recover(parser, src); - - let expression_kind = expression_kind.unwrap(); - let ExpressionKind::If(if_expression) = expression_kind else { - panic!("Expected an if expression, got {:?}", expression_kind); - }; - - assert_eq!(if_expression.consequence.kind, ExpressionKind::Error); - assert_eq!(if_expression.alternative, None); - - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected { after if condition"); + fn eat_assign(&mut self) -> bool { + self.eat(Token::Assign) } - #[test] - fn parse_module_declaration() { - parse_with(module_declaration(), "mod foo").unwrap(); - parse_with(module_declaration(), "#[attr] mod foo").unwrap(); - parse_with(module_declaration(), "mod 1").unwrap_err(); + fn eat_dot(&mut self) -> bool { + self.eat(Token::Dot) } - #[test] - fn parse_submodule_declaration() { - parse_with(submodule(module()), "mod foo {}").unwrap(); - parse_with(submodule(module()), "#[attr] mod foo {}").unwrap(); + fn eat_pipe(&mut self) -> bool { + self.eat(Token::Pipe) } - #[test] - fn parse_contract() { - parse_with(contract(module()), "contract foo {}").unwrap(); - parse_with(contract(module()), "#[attr] contract foo {}").unwrap(); + fn eat(&mut self, token: Token) -> bool { + if self.token.token() == &token { + self.bump(); + true + } else { + false + } } - #[test] - fn parse_use() { - let valid_use_statements = [ - "use std::hash", - "use std", - "use foo::bar as hello", - "use bar as bar", - "use foo::{}", - "use foo::{bar,}", - "use foo::{bar, hello}", - "use foo::{bar as bar2, hello}", - "use foo::{bar as bar2, hello::{foo}, nested::{foo, bar}}", - "use std::{println, bar::baz}", - ]; - - let invalid_use_statements = [ - "use std as ;", - "use foobar as as;", - "use hello:: as foo;", - "use foo bar::baz", - "use foo bar::{baz}", - "use foo::{,}", - ]; - - let use_statements = valid_use_statements - .into_iter() - .map(|valid_str| (valid_str, true)) - .chain(invalid_use_statements.into_iter().map(|invalid_str| (invalid_str, false))); - - for (use_statement_str, expect_valid) in use_statements { - let mut use_statement_str = use_statement_str.to_string(); - if expect_valid { - let (result_opt, _diagnostics) = - parse_recover(&use_statement(), &use_statement_str); - use_statement_str.push(';'); - match result_opt.unwrap() { - TopLevelStatementKind::Import(expected_use_statement, _visibility) => { - Some(expected_use_statement) - } - _ => unreachable!(), - } - } else { - let result = parse_with(&use_statement(), &use_statement_str); - assert!(result.is_err()); - None - }; + fn eat_keyword_or_error(&mut self, keyword: Keyword) { + if !self.eat_keyword(keyword) { + self.expected_token(Token::Keyword(keyword)); } } - #[test] - fn parse_type_aliases() { - let cases = vec!["type foo = u8", "type bar = String", "type baz = Vec"]; - parse_all(type_alias_definition(), cases); - - let failing = vec!["type = u8", "type foo", "type foo = 1"]; - parse_all_failing(type_alias_definition(), failing); + fn eat_or_error(&mut self, token: Token) { + if !self.eat(token.clone()) { + self.expected_token(token); + } } - #[test] - fn parse_member_access() { - let cases = vec!["a.b", "a + b.c", "foo.bar as u32"]; - parse_all(expression(), cases); + fn at(&self, token: Token) -> bool { + self.token.token() == &token } - #[test] - fn parse_constructor() { - let cases = vec![ - "Baz", - "Bar { ident: 32 }", - "Baz { other: 2 + 42, ident: foo() + 1 }", - "Baz { other, ident: foo() + 1, foo }", - ]; - - parse_all(expression(), cases); - parse_with(expression(), "Foo { a + b }").unwrap_err(); + fn at_keyword(&self, keyword: Keyword) -> bool { + self.at(Token::Keyword(keyword)) } - // Semicolons are: - // - Required after non-expression statements - // - Optional after for, if, block expressions - // - Optional after an expression as the last statement of a block - // - Required after an expression as the non-final statement of a block - #[test] - fn parse_semicolons() { - let cases = vec![ - "{ if true {} if false {} foo }", - "{ if true {}; if false {} foo }", - "{ for x in 0..1 {} if false {} foo; }", - "{ let x = 2; }", - "{ expr1; expr2 }", - "{ expr1; expr2; }", - ]; - parse_all(block(fresh_statement()), cases); - - let failing = vec![ - // We disallow multiple semicolons after a statement unlike rust where it is a warning - "{ test;; foo }", - "{ for x in 0..1 {} foo if false {} }", - "{ let x = 2 }", - "{ expr1 expr2 }", - ]; - parse_all_failing(block(fresh_statement()), failing); + fn next_is(&self, token: Token) -> bool { + self.next_token.token() == &token } - #[test] - fn statement_recovery() { - let cases = vec![ - Case { source: "let a = 4 + 3", expect: "let a = (4 + 3)", errors: 0 }, - Case { source: "let a: = 4 + 3", expect: "let a: error = (4 + 3)", errors: 1 }, - Case { source: "let = 4 + 3", expect: "let $error = (4 + 3)", errors: 1 }, - Case { source: "let = ", expect: "let $error = Error", errors: 2 }, - Case { source: "let", expect: "let $error = Error", errors: 3 }, - Case { source: "foo = one two three", expect: "foo = one", errors: 1 }, - Case { source: "constrain", expect: "constrain Error", errors: 2 }, - Case { source: "assert", expect: "assert()", errors: 1 }, - Case { source: "constrain x ==", expect: "constrain (x == Error)", errors: 2 }, - Case { source: "assert(x ==)", expect: "assert((x == Error))", errors: 1 }, - Case { source: "assert(x == x, x)", expect: "assert((x == x), x)", errors: 0 }, - Case { source: "assert_eq(x,)", expect: "assert_eq(x)", errors: 0 }, - Case { source: "assert_eq(x, x, x, x)", expect: "assert_eq(x, x, x, x)", errors: 0 }, - Case { source: "assert_eq(x, x, x)", expect: "assert_eq(x, x, x)", errors: 0 }, - ]; - - check_cases_with_errors(&cases[..], fresh_statement()); + fn at_eof(&self) -> bool { + self.token.token() == &Token::EOF } - #[test] - fn return_validation() { - let cases = [ - Case { - source: "{ return 42; }", - expect: concat!("{\n", " Error\n", "}",), - errors: 1, - }, - Case { - source: "{ return 1; return 2; }", - expect: concat!("{\n", " Error\n", " Error\n", "}"), - errors: 2, - }, - Case { - source: "{ return 123; let foo = 4 + 3; }", - expect: concat!("{\n", " Error\n", " let foo = (4 + 3)\n", "}"), - errors: 1, - }, - Case { - source: "{ return 1 + 2 }", - expect: concat!("{\n", " Error\n", "}",), - errors: 2, - }, - Case { source: "{ return; }", expect: concat!("{\n", " Error\n", "}",), errors: 1 }, - ]; - - check_cases_with_errors(&cases[..], block(fresh_statement())); + fn span_since(&self, start_span: Span) -> Span { + if self.current_token_span == start_span { + start_span + } else { + let end_span = self.previous_token_span; + Span::from(start_span.start()..end_span.end()) + } } - #[test] - fn expr_no_constructors() { - let cases = [ - Case { - source: "{ if structure { a: 1 } {} }", - expect: concat!( - "{\n", - " if structure {\n", - " Error\n", - " }\n", - " {\n", - " }\n", - "}", - ), - errors: 1, - }, - Case { - source: "{ if ( structure { a: 1 } ) {} }", - expect: concat!("{\n", " if ((structure { a: 1 })) {\n", " }\n", "}",), - errors: 0, - }, - Case { - source: "{ if ( structure {} ) {} }", - expect: concat!("{\n", " if ((structure { })) {\n", " }\n", "}"), - errors: 0, - }, - Case { - source: "{ if (a { x: 1 }, b { y: 2 }) {} }", - expect: concat!("{\n", " if ((a { x: 1 }), (b { y: 2 })) {\n", " }\n", "}",), - errors: 0, - }, - Case { - source: "{ if ({ let foo = bar { baz: 42 }; foo == bar { baz: 42 }}) {} }", - expect: concat!( - "{\n", - " if ({\n", - " let foo = (bar { baz: 42 })\n", - " (foo == (bar { baz: 42 }))\n", - " }) {\n", - " }\n", - "}", - ), - errors: 0, - }, - ]; - - check_cases_with_errors(&cases[..], block(fresh_statement())); + fn span_at_previous_token_end(&self) -> Span { + Span::from(self.previous_token_span.end()..self.previous_token_span.end()) } - #[test] - fn test_quote() { - let cases = vec![ - "quote {}", - "quote { a.b }", - "quote { ) ( }", // invalid syntax is fine in a quote - "quote { { } }", // Nested `{` and `}` shouldn't close the quote as long as they are matched. - "quote { 1 { 2 { 3 { 4 { 5 } 4 4 } 3 3 } 2 2 } 1 1 }", - ]; - parse_all(quote(), cases); - - let failing = vec!["quote {}}", "quote a", "quote { { { } } } }"]; - parse_all_failing(quote(), failing); + fn expected_identifier(&mut self) { + self.expected_label(ParsingRuleLabel::Identifier); } - #[test] - fn test_parses_block_statement_not_infix_expression() { - let src = r#" - { - {} - -1 - }"#; - let (block_expr, _) = parse_recover(block(fresh_statement()), src); - let block_expr = block_expr.expect("Failed to parse module"); - assert_eq!(block_expr.statements.len(), 2); + fn expected_token(&mut self, token: Token) { + self.errors.push(ParserError::expected_token( + token, + self.token.token().clone(), + self.current_token_span, + )); } - #[test] - fn test_parses_if_statement_not_infix_expression() { - let src = r#" - { - if 1 { 2 } else { 3 } - -1 - }"#; - let (block_expr, _) = parse_recover(block(fresh_statement()), src); - let block_expr = block_expr.expect("Failed to parse module"); - assert_eq!(block_expr.statements.len(), 2); + fn expected_one_of_tokens(&mut self, tokens: &[Token]) { + self.errors.push(ParserError::expected_one_of_tokens( + tokens, + self.token.token().clone(), + self.current_token_span, + )); } - #[test] - fn test_parses_if_statement_followed_by_tuple_as_two_separate_statements() { - // Regression for #1310: this should not be parsed as a function call - let src = r#" - { - if 1 { 2 } else { 3 } (1, 2) - }"#; - let (block_expr, _) = parse_recover(block(fresh_statement()), src); - let block_expr = block_expr.expect("Failed to parse module"); - assert_eq!(block_expr.statements.len(), 2); + fn expected_label(&mut self, label: ParsingRuleLabel) { + self.errors.push(ParserError::expected_label( + label, + self.token.token().clone(), + self.current_token_span, + )); } - #[test] - fn test_parses_member_access_without_member_name() { - let src = "{ foo. }"; + fn expected_token_separating_items(&mut self, token: Token, items: &'static str, span: Span) { + self.push_error(ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items }, span); + } - let (Some(block_expression), errors) = parse_recover(block(fresh_statement()), src) else { - panic!("Expected to be able to parse a block expression"); - }; + fn modifiers_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + self.visibility_not_followed_by_an_item(modifiers); + self.unconstrained_not_followed_by_an_item(modifiers); + self.comptime_not_followed_by_an_item(modifiers); + } - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected an identifier after ."); + fn visibility_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + if modifiers.visibility != ItemVisibility::Private { + self.push_error( + ParserErrorReason::VisibilityNotFollowedByAnItem { + visibility: modifiers.visibility, + }, + modifiers.visibility_span, + ); + } + } - let statement = &block_expression.statements[0]; - let StatementKind::Expression(expr) = &statement.kind else { - panic!("Expected an expression statement"); - }; + fn unconstrained_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.unconstrained { + self.push_error(ParserErrorReason::UnconstrainedNotFollowedByAnItem, span); + } + } - let ExpressionKind::Variable(var) = &expr.kind else { - panic!("Expected a variable expression"); - }; + fn comptime_not_followed_by_an_item(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.comptime { + self.push_error(ParserErrorReason::ComptimeNotFollowedByAnItem, span); + } + } - assert_eq!(var.to_string(), "foo"); + fn comptime_mutable_and_unconstrained_not_applicable(&mut self, modifiers: Modifiers) { + self.mutable_not_applicable(modifiers); + self.comptime_not_applicable(modifiers); + self.unconstrained_not_applicable(modifiers); } - #[test] - fn parse_recover_impl_without_body() { - let src = "impl Foo"; + fn mutable_not_applicable(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.mutable { + self.push_error(ParserErrorReason::MutableNotApplicable, span); + } + } - let (top_level_statement, errors) = parse_recover(implementation(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected <, where or { after impl type"); + fn comptime_not_applicable(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.comptime { + self.push_error(ParserErrorReason::ComptimeNotApplicable, span); + } + } - let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatementKind::Impl(impl_) = top_level_statement else { - panic!("Expected to parse an impl"); - }; + fn unconstrained_not_applicable(&mut self, modifiers: Modifiers) { + if let Some(span) = modifiers.unconstrained { + self.push_error(ParserErrorReason::UnconstrainedNotApplicable, span); + } + } - assert_eq!(impl_.object_type.to_string(), "Foo"); - assert!(impl_.methods.is_empty()); + fn push_error(&mut self, reason: ParserErrorReason, span: Span) { + self.errors.push(ParserError::with_reason(reason, span)); } } + +fn eof_spanned_token() -> SpannedToken { + SpannedToken::new(Token::EOF, Default::default()) +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/arguments.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/arguments.rs new file mode 100644 index 00000000000..380f42809a6 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/arguments.rs @@ -0,0 +1,40 @@ +use crate::{ast::Expression, token::Token}; + +use super::{parse_many::separated_by_comma_until_right_paren, Parser}; + +pub(crate) struct CallArguments { + pub(crate) arguments: Vec, + pub(crate) is_macro_call: bool, +} + +impl<'a> Parser<'a> { + /// Arguments = '(' ArgumentsList? ')' + /// + /// ArgumentsList = Expression ( ',' Expression )? ','? + pub(crate) fn parse_arguments(&mut self) -> Option> { + if !self.eat_left_paren() { + return None; + } + + let arguments = self.parse_many( + "arguments", + separated_by_comma_until_right_paren(), + Self::parse_expression_in_list, + ); + + Some(arguments) + } + + /// CallArguments = '!'? Arguments + pub(super) fn parse_call_arguments(&mut self) -> Option { + let is_macro_call = self.at(Token::Bang) && self.next_is(Token::LeftParen); + + if is_macro_call { + // Given that we expected '!' '(', it's safe to skip the '!' because the next + // `self.parse_arguments()` will always return `Some`. + self.bump(); + } + + self.parse_arguments().map(|arguments| CallArguments { arguments, is_macro_call }) + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs deleted file mode 100644 index 9eb429ef295..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/assertion.rs +++ /dev/null @@ -1,196 +0,0 @@ -use crate::ast::StatementKind; -use crate::parser::{ignore_then_commit, then_commit, ParserError, ParserErrorReason}; -use crate::parser::{labels::ParsingRuleLabel, parenthesized, ExprParser, NoirParser}; - -use crate::ast::{ConstrainKind, ConstrainStatement}; -use crate::token::{Keyword, Token}; - -use chumsky::prelude::*; - -use super::keyword; - -pub(super) fn constrain<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - ignore_then_commit( - keyword(Keyword::Constrain).labelled(ParsingRuleLabel::Statement), - expr_parser, - ) - .map_with_span(|expr, span| { - StatementKind::Constrain(ConstrainStatement { - kind: ConstrainKind::Constrain, - arguments: vec![expr], - span, - }) - }) - .validate(|expr, span, emit| { - emit(ParserError::with_reason(ParserErrorReason::ConstrainDeprecated, span)); - expr - }) -} - -pub(super) fn assertion<'a, P>(expr_parser: P) -> impl NoirParser + 'a -where - P: ExprParser + 'a, -{ - let keyword = choice(( - keyword(Keyword::Assert).map(|_| ConstrainKind::Assert), - keyword(Keyword::AssertEq).map(|_| ConstrainKind::AssertEq), - )); - - let argument_parser = expr_parser.separated_by(just(Token::Comma)).allow_trailing(); - - then_commit(keyword, parenthesized(argument_parser)) - .labelled(ParsingRuleLabel::Statement) - .map_with_span(|(kind, arguments), span| { - StatementKind::Constrain(ConstrainStatement { arguments, kind, span }) - }) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::{ - ast::{BinaryOpKind, ExpressionKind, Literal}, - parser::parser::{ - expression, - test_helpers::{parse_all, parse_all_failing, parse_with}, - }, - }; - - /// Deprecated constrain usage test - #[test] - fn parse_constrain() { - let errors = parse_with(constrain(expression()), "constrain x == y").unwrap_err(); - assert_eq!(errors.len(), 1); - assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); - - // Currently we disallow constrain statements where the outer infix operator - // produces a value. This would require an implicit `==` which - // may not be intuitive to the user. - // - // If this is deemed useful, one would either apply a transformation - // or interpret it with an `==` in the evaluator - let disallowed_operators = vec![ - BinaryOpKind::And, - BinaryOpKind::Subtract, - BinaryOpKind::Divide, - BinaryOpKind::Multiply, - BinaryOpKind::Or, - ]; - - for operator in disallowed_operators { - let src = format!("constrain x {} y;", operator.as_string()); - let errors = parse_with(constrain(expression()), &src).unwrap_err(); - assert_eq!(errors.len(), 2); - assert!(format!("{}", errors.first().unwrap()).contains("deprecated")); - } - - // These are general cases which should always work. - // - // The first case is the most noteworthy. It contains two `==` - // The first (inner) `==` is a predicate which returns 0/1 - // The outer layer is an infix `==` which is - // associated with the Constrain statement - let errors = parse_all_failing( - constrain(expression()), - vec![ - "constrain ((x + y) == k) + z == y", - "constrain (x + !y) == y", - "constrain (x ^ y) == y", - "constrain (x ^ y) == (y + m)", - "constrain x + x ^ x == y | m", - ], - ); - assert_eq!(errors.len(), 5); - assert!(errors - .iter() - .all(|err| { err.is_error() && err.to_string().contains("deprecated") })); - } - - /// This is the standard way to declare an assert statement - #[test] - fn parse_assert() { - parse_with(assertion(expression()), "assert(x == y)").unwrap(); - - // Currently we disallow constrain statements where the outer infix operator - // produces a value. This would require an implicit `==` which - // may not be intuitive to the user. - // - // If this is deemed useful, one would either apply a transformation - // or interpret it with an `==` in the evaluator - let disallowed_operators = vec![ - BinaryOpKind::And, - BinaryOpKind::Subtract, - BinaryOpKind::Divide, - BinaryOpKind::Multiply, - BinaryOpKind::Or, - ]; - - for operator in disallowed_operators { - let src = format!("assert(x {} y);", operator.as_string()); - parse_with(assertion(expression()), &src).unwrap_err(); - } - - // These are general cases which should always work. - // - // The first case is the most noteworthy. It contains two `==` - // The first (inner) `==` is a predicate which returns 0/1 - // The outer layer is an infix `==` which is - // associated with the Constrain statement - parse_all( - assertion(expression()), - vec![ - "assert(((x + y) == k) + z == y)", - "assert((x + !y) == y)", - "assert((x ^ y) == y)", - "assert((x ^ y) == (y + m))", - "assert(x + x ^ x == y | m)", - ], - ); - - match parse_with(assertion(expression()), "assert(x == y, \"assertion message\")").unwrap() - { - StatementKind::Constrain(ConstrainStatement { arguments, .. }) => { - let message = arguments.last().unwrap(); - match &message.kind { - ExpressionKind::Literal(Literal::Str(message_string)) => { - assert_eq!(message_string, "assertion message"); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } - - /// This is the standard way to assert that two expressions are equivalent - #[test] - fn parse_assert_eq() { - parse_all( - assertion(expression()), - vec![ - "assert_eq(x, y)", - "assert_eq(((x + y) == k) + z, y)", - "assert_eq(x + !y, y)", - "assert_eq(x ^ y, y)", - "assert_eq(x ^ y, y + m)", - "assert_eq(x + x ^ x, y | m)", - ], - ); - match parse_with(assertion(expression()), "assert_eq(x, y, \"assertion message\")").unwrap() - { - StatementKind::Constrain(ConstrainStatement { arguments, .. }) => { - let message = arguments.last().unwrap(); - match &message.kind { - ExpressionKind::Literal(Literal::Str(message_string)) => { - assert_eq!(message_string, "assertion message"); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - } - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs index dc363248d72..d0f7221a6be 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/attributes.rs @@ -1,77 +1,81 @@ -use chumsky::Parser; use noirc_errors::Span; -use crate::{ - parser::{NoirParser, ParserError, ParserErrorReason}, - token::{Attribute, Attributes, SecondaryAttribute, Token, TokenKind}, -}; +use crate::parser::ParserErrorReason; +use crate::token::SecondaryAttribute; +use crate::token::{Attribute, Token, TokenKind}; -use super::primitives::token_kind; +use super::parse_many::without_separator; +use super::Parser; -fn attribute() -> impl NoirParser { - token_kind(TokenKind::Attribute).map(|token| match token { - Token::Attribute(attribute) => attribute, - _ => unreachable!("Parser should have already errored due to token not being an attribute"), - }) -} +impl<'a> Parser<'a> { + /// InnerAttribute = inner_attribute + pub(super) fn parse_inner_attribute(&mut self) -> Option { + let token = self.eat_kind(TokenKind::InnerAttribute)?; + match token.into_token() { + Token::InnerAttribute(attribute) => Some(attribute), + _ => unreachable!(), + } + } -pub(super) fn attributes() -> impl NoirParser> { - attribute().repeated() -} + /// Attributes = attribute* + pub(super) fn parse_attributes(&mut self) -> Vec<(Attribute, Span)> { + self.parse_many("attributes", without_separator(), Self::parse_attribute) + } -pub(super) fn validate_attributes( - attributes: Vec, - span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Attributes { - let mut primary = None; - let mut secondary = Vec::new(); + fn parse_attribute(&mut self) -> Option<(Attribute, Span)> { + self.eat_kind(TokenKind::Attribute).map(|token| match token.into_token() { + Token::Attribute(attribute) => (attribute, self.previous_token_span), + _ => unreachable!(), + }) + } - for attribute in attributes { - match attribute { - Attribute::Function(attr) => { - if primary.is_some() { - emit(ParserError::with_reason( - ParserErrorReason::MultipleFunctionAttributesFound, - span, - )); + pub(super) fn validate_secondary_attributes( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Vec { + attributes + .into_iter() + .filter_map(|(attribute, span)| match attribute { + Attribute::Function(..) => { + self.push_error(ParserErrorReason::NoFunctionAttributesAllowedOnStruct, span); + None } - primary = Some(attr); - } - Attribute::Secondary(attr) => secondary.push(attr), - } + Attribute::Secondary(attr) => Some(attr), + }) + .collect() } - - Attributes { function: primary, secondary } } -pub(super) fn validate_secondary_attributes( - attributes: Vec, - span: Span, - emit: &mut dyn FnMut(ParserError), -) -> Vec { - let mut struct_attributes = vec![]; +#[cfg(test)] +mod tests { + use crate::{ + parser::{parser::tests::expect_no_errors, Parser}, + token::{Attribute, FunctionAttribute, SecondaryAttribute, TestScope}, + }; - for attribute in attributes { - match attribute { - Attribute::Function(..) => { - emit(ParserError::with_reason( - ParserErrorReason::NoFunctionAttributesAllowedOnStruct, - span, - )); - } - Attribute::Secondary(attr) => struct_attributes.push(attr), - } + #[test] + fn parses_inner_attribute() { + let src = "#![hello]"; + let mut parser = Parser::for_str(src); + let Some(SecondaryAttribute::Custom(custom)) = parser.parse_inner_attribute() else { + panic!("Expected inner custom attribute"); + }; + expect_no_errors(&parser.errors); + assert_eq!(custom.contents, "hello"); } - struct_attributes -} + #[test] + fn parses_attributes() { + let src = "#[test] #[deprecated]"; + let mut parser = Parser::for_str(src); + let mut attributes = parser.parse_attributes(); + expect_no_errors(&parser.errors); + assert_eq!(attributes.len(), 2); + + let (attr, _) = attributes.remove(0); + assert!(matches!(attr, Attribute::Function(FunctionAttribute::Test(TestScope::None)))); -pub(super) fn inner_attribute() -> impl NoirParser { - token_kind(TokenKind::InnerAttribute).map(|token| match token { - Token::InnerAttribute(attribute) => attribute, - _ => unreachable!( - "Parser should have already errored due to token not being an inner attribute" - ), - }) + let (attr, _) = attributes.remove(0); + assert!(matches!(attr, Attribute::Secondary(SecondaryAttribute::Deprecated(None)))); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs index 151ff21017f..578a49641f6 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/doc_comments.rs @@ -1,36 +1,58 @@ -use chumsky::Parser; - -use crate::{ - parser::NoirParser, - token::{DocStyle, Token, TokenKind}, -}; - -use super::primitives::token_kind; - -fn outer_doc_comment() -> impl NoirParser { - token_kind(TokenKind::OuterDocComment).map(|token| match token { - Token::LineComment(comment, Some(DocStyle::Outer)) => comment, - Token::BlockComment(comment, Some(DocStyle::Outer)) => comment, - _ => unreachable!( - "Parser should have already errored due to token not being an outer doc comment" - ), - }) -} +use crate::token::{DocStyle, Token, TokenKind}; -pub(super) fn outer_doc_comments() -> impl NoirParser> { - outer_doc_comment().repeated() -} +use super::{parse_many::without_separator, Parser}; + +impl<'a> Parser<'a> { + /// InnerDocComments = inner_doc_comment* + pub(super) fn parse_inner_doc_comments(&mut self) -> Vec { + self.parse_many("inner doc comments", without_separator(), Self::parse_inner_doc_comment) + } + + fn parse_inner_doc_comment(&mut self) -> Option { + self.eat_kind(TokenKind::InnerDocComment).map(|token| match token.into_token() { + Token::LineComment(comment, Some(DocStyle::Inner)) + | Token::BlockComment(comment, Some(DocStyle::Inner)) => comment, + _ => unreachable!(), + }) + } -fn inner_doc_comment() -> impl NoirParser { - token_kind(TokenKind::InnerDocComment).map(|token| match token { - Token::LineComment(comment, Some(DocStyle::Inner)) => comment, - Token::BlockComment(comment, Some(DocStyle::Inner)) => comment, - _ => unreachable!( - "Parser should have already errored due to token not being an inner doc comment" - ), - }) + /// OuterDocComments = outer_doc_comments* + pub(super) fn parse_outer_doc_comments(&mut self) -> Vec { + self.parse_many("outer doc comments", without_separator(), Self::parse_outer_doc_comment) + } + + fn parse_outer_doc_comment(&mut self) -> Option { + self.eat_kind(TokenKind::OuterDocComment).map(|token| match token.into_token() { + Token::LineComment(comment, Some(DocStyle::Outer)) + | Token::BlockComment(comment, Some(DocStyle::Outer)) => comment, + _ => unreachable!(), + }) + } } -pub(super) fn inner_doc_comments() -> impl NoirParser> { - inner_doc_comment().repeated() +#[cfg(test)] +mod tests { + use crate::parser::{parser::tests::expect_no_errors, Parser}; + + #[test] + fn parses_inner_doc_comments() { + let src = "//! Hello\n//! World"; + let mut parser = Parser::for_str(src); + let comments = parser.parse_inner_doc_comments(); + expect_no_errors(&parser.errors); + assert_eq!(comments.len(), 2); + assert_eq!(comments[0], " Hello"); + assert_eq!(comments[1], " World"); + } + + #[test] + fn parses_outer_doc_comments() { + let src = "/// Hello\n/// World"; + let mut parser = Parser::for_str(src); + let comments = parser.parse_outer_doc_comments(); + expect_no_errors(&parser.errors); + assert_eq!(comments.len(), 2); + assert_eq!(comments[0], " Hello"); + assert_eq!(comments[1], " World"); + } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs new file mode 100644 index 00000000000..e2b942faebf --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/expression.rs @@ -0,0 +1,1593 @@ +use iter_extended::vecmap; +use noirc_errors::Span; + +use crate::{ + ast::{ + ArrayLiteral, BlockExpression, CallExpression, CastExpression, ConstructorExpression, + Expression, ExpressionKind, GenericTypeArgs, Ident, IfExpression, IndexExpression, Literal, + MemberAccessExpression, MethodCallExpression, Statement, TypePath, UnaryOp, UnresolvedType, + }, + parser::{labels::ParsingRuleLabel, parser::parse_many::separated_by_comma, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{ + parse_many::{ + separated_by_comma_until_right_brace, separated_by_comma_until_right_paren, + without_separator, + }, + Parser, +}; + +impl<'a> Parser<'a> { + pub(crate) fn parse_expression_or_error(&mut self) -> Expression { + self.parse_expression_or_error_impl(true) // allow constructors + } + + /// Expression = EqualOrNotEqualExpression + pub(crate) fn parse_expression(&mut self) -> Option { + self.parse_expression_impl(true) // allow constructors + } + + /// When parsing `if` conditions we don't allow constructors. + /// For example `if foo { 1 }` shouldn't have `foo { 1 }` as the condition, but `foo` instead. + /// The same goes with `for`: `for x in foo { 1 }` should have `foo` as the collection, not `foo { 1 }`. + /// + /// ExpressionExceptConstructor = "Expression except ConstructorException" + pub(crate) fn parse_expression_except_constructor_or_error(&mut self) -> Expression { + self.parse_expression_or_error_impl(false) // allow constructors + } + + pub(crate) fn parse_expression_or_error_impl( + &mut self, + allow_constructors: bool, + ) -> Expression { + if let Some(expr) = self.parse_expression_impl(allow_constructors) { + expr + } else { + self.push_expected_expression(); + Expression { kind: ExpressionKind::Error, span: self.span_at_previous_token_end() } + } + } + + fn parse_expression_impl(&mut self, allow_constructors: bool) -> Option { + self.parse_equal_or_not_equal(allow_constructors) + } + + /// Term + /// = UnaryOp Term + /// | AtomOrUnaryRightExpression + pub(super) fn parse_term(&mut self, allow_constructors: bool) -> Option { + let start_span = self.current_token_span; + + if let Some(operator) = self.parse_unary_op() { + let Some(rhs) = self.parse_term(allow_constructors) else { + self.expected_label(ParsingRuleLabel::Expression); + return None; + }; + let kind = ExpressionKind::prefix(operator, rhs); + let span = self.span_since(start_span); + return Some(Expression { kind, span }); + } + + self.parse_atom_or_unary_right(allow_constructors) + } + + /// UnaryOp = '&' 'mut' | '-' | '!' | '*' + fn parse_unary_op(&mut self) -> Option { + if self.at(Token::Ampersand) && self.next_is(Token::Keyword(Keyword::Mut)) { + self.bump(); + self.bump(); + Some(UnaryOp::MutableReference) + } else if self.eat(Token::Minus) { + Some(UnaryOp::Minus) + } else if self.eat(Token::Bang) { + Some(UnaryOp::Not) + } else if self.eat(Token::Star) { + Some(UnaryOp::Dereference { implicitly_added: false }) + } else { + None + } + } + + /// AtomOrUnaryRightExpression + /// = Atom + /// | UnaryRightExpression + fn parse_atom_or_unary_right(&mut self, allow_constructors: bool) -> Option { + let start_span = self.current_token_span; + let mut atom = self.parse_atom(allow_constructors)?; + let mut parsed; + + loop { + (atom, parsed) = self.parse_unary_right(atom, start_span); + if parsed { + continue; + } else { + break; + } + } + + Some(atom) + } + + /// UnaryRightExpression + /// = CallExpression + /// | MemberAccessOrMethodCallExpression + /// | CastExpression + /// | IndexExpression + fn parse_unary_right(&mut self, mut atom: Expression, start_span: Span) -> (Expression, bool) { + let mut parsed; + + (atom, parsed) = self.parse_call(atom, start_span); + if parsed { + return (atom, parsed); + } + + (atom, parsed) = self.parse_member_access_or_method_call(atom, start_span); + if parsed { + return (atom, parsed); + } + + (atom, parsed) = self.parse_cast(atom, start_span); + if parsed { + return (atom, parsed); + } + + self.parse_index(atom, start_span) + } + + /// CallExpression = Atom CallArguments + fn parse_call(&mut self, atom: Expression, start_span: Span) -> (Expression, bool) { + if let Some(call_arguments) = self.parse_call_arguments() { + let kind = ExpressionKind::Call(Box::new(CallExpression { + func: Box::new(atom), + arguments: call_arguments.arguments, + is_macro_call: call_arguments.is_macro_call, + })); + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } else { + (atom, false) + } + } + + /// MemberAccessOrMethodCallExpression + /// = MemberAccessExpression + /// | MethodCallExpression + /// + /// MemberAccessExpression = Atom '.' identifier + /// + /// MethodCallExpression = Atom '.' identifier CallArguments + fn parse_member_access_or_method_call( + &mut self, + atom: Expression, + start_span: Span, + ) -> (Expression, bool) { + if !self.eat_dot() { + return (atom, false); + } + + let Some(field_name) = self.parse_member_access_field_name() else { return (atom, true) }; + + let generics = self.parse_generics_after_member_access_field_name(); + + let kind = if let Some(call_arguments) = self.parse_call_arguments() { + ExpressionKind::MethodCall(Box::new(MethodCallExpression { + object: atom, + method_name: field_name, + generics, + arguments: call_arguments.arguments, + is_macro_call: call_arguments.is_macro_call, + })) + } else { + ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs: atom, + rhs: field_name, + })) + }; + + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } + + fn parse_member_access_field_name(&mut self) -> Option { + if let Some(ident) = self.eat_ident() { + Some(ident) + } else if let Some(int) = self.eat_int() { + Some(Ident::new(int.to_string(), self.previous_token_span)) + } else { + self.push_error( + ParserErrorReason::ExpectedFieldName(self.token.token().clone()), + self.current_token_span, + ); + None + } + } + + /// CastExpression = Atom 'as' Type + fn parse_cast(&mut self, atom: Expression, start_span: Span) -> (Expression, bool) { + if !self.eat_keyword(Keyword::As) { + return (atom, false); + } + + let typ = self.parse_type_or_error(); + let kind = ExpressionKind::Cast(Box::new(CastExpression { lhs: atom, r#type: typ })); + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } + + /// IndexExpression = Atom '[' Expression ']' + fn parse_index(&mut self, atom: Expression, start_span: Span) -> (Expression, bool) { + if !self.eat_left_bracket() { + return (atom, false); + } + + let index = self.parse_expression_or_error(); + self.eat_or_error(Token::RightBracket); + let kind = ExpressionKind::Index(Box::new(IndexExpression { collection: atom, index })); + let span = self.span_since(start_span); + let atom = Expression { kind, span }; + (atom, true) + } + + fn parse_generics_after_member_access_field_name(&mut self) -> Option> { + if self.eat_double_colon() { + let generics = + self.parse_path_generics(ParserErrorReason::AssociatedTypesNotAllowedInMethodCalls); + if generics.is_none() { + self.expected_token(Token::Less); + } + generics + } else { + None + } + } + + /// Atom + /// = Literal + /// | ParenthesesExpression + /// | UnsafeExpression + /// | PathExpression + /// | IfExpression + /// | Lambda + /// | ComptimeExpression + /// | UnquoteExpression + /// | TypePathExpression + /// | AsTraitPath + /// | ResolvedExpression + /// | InternedExpression + /// | InternedStatementExpression + fn parse_atom(&mut self, allow_constructors: bool) -> Option { + let start_span = self.current_token_span; + let kind = self.parse_atom_kind(allow_constructors)?; + Some(Expression { kind, span: self.span_since(start_span) }) + } + + fn parse_atom_kind(&mut self, allow_constructors: bool) -> Option { + if let Some(literal) = self.parse_literal() { + return Some(literal); + } + + if let Some(kind) = self.parse_parentheses_expression() { + return Some(kind); + } + + if let Some(kind) = self.parse_unsafe_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_path_expr(allow_constructors) { + return Some(kind); + } + + // A constructor where the type is an interned unresolved type data is valid + if matches!(self.token.token(), Token::InternedUnresolvedTypeData(..)) + && self.next_is(Token::LeftBrace) + { + let span = self.current_token_span; + let typ = self.parse_interned_type().unwrap(); + self.eat_or_error(Token::LeftBrace); + let typ = UnresolvedType { typ, span }; + return Some(self.parse_constructor(typ)); + } + + if let Some(kind) = self.parse_if_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_lambda() { + return Some(kind); + } + + if let Some(kind) = self.parse_comptime_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_unquote_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_type_path_expr() { + return Some(kind); + } + + if let Some(as_trait_path) = self.parse_as_trait_path() { + return Some(ExpressionKind::AsTraitPath(as_trait_path)); + } + + if let Some(kind) = self.parse_resolved_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_interned_expr() { + return Some(kind); + } + + if let Some(kind) = self.parse_interned_statement_expr() { + return Some(kind); + } + + None + } + + /// ResolvedExpression = unquote_marker + fn parse_resolved_expr(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::UnquoteMarker) { + match token.into_token() { + Token::UnquoteMarker(expr_id) => return Some(ExpressionKind::Resolved(expr_id)), + _ => unreachable!(""), + } + } + + None + } + + /// InternedExpression = interned_expr + fn parse_interned_expr(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::InternedExpr) { + match token.into_token() { + Token::InternedExpr(id) => return Some(ExpressionKind::Interned(id)), + _ => unreachable!(""), + } + } + + None + } + + /// InternedStatementExpression = interned_statement + fn parse_interned_statement_expr(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::InternedStatement) { + match token.into_token() { + Token::InternedStatement(id) => return Some(ExpressionKind::InternedStatement(id)), + _ => unreachable!(""), + } + } + + None + } + + /// UnsafeExpression = 'unsafe' Block + fn parse_unsafe_expr(&mut self) -> Option { + if !self.eat_keyword(Keyword::Unsafe) { + return None; + } + + let start_span = self.current_token_span; + if let Some(block) = self.parse_block() { + Some(ExpressionKind::Unsafe(block, self.span_since(start_span))) + } else { + Some(ExpressionKind::Error) + } + } + + /// PathExpression + /// = VariableExpression + /// | ConstructorExpression + /// + /// VariableExpression = Path + fn parse_path_expr(&mut self, allow_constructors: bool) -> Option { + let Some(path) = self.parse_path() else { + return None; + }; + + if allow_constructors && self.eat_left_brace() { + let typ = UnresolvedType::from_path(path); + return Some(self.parse_constructor(typ)); + } + + Some(ExpressionKind::Variable(path)) + } + + /// ConstructorExpression = Type '{' ConstructorFields? '}' + /// + /// ConstructorFields = ConstructorField ( ',' ConstructorField )* ','? + /// + /// ConstructorField = identifier ( ':' Expression )? + fn parse_constructor(&mut self, typ: UnresolvedType) -> ExpressionKind { + let fields = self.parse_many( + "constructor fields", + separated_by_comma_until_right_brace(), + Self::parse_constructor_field, + ); + + ExpressionKind::Constructor(Box::new(ConstructorExpression { + typ, + fields, + struct_type: None, + })) + } + + fn parse_constructor_field(&mut self) -> Option<(Ident, Expression)> { + let Some(ident) = self.eat_ident() else { + return None; + }; + + Some(if self.eat_colon() { + let expression = self.parse_expression_or_error(); + (ident, expression) + } else { + (ident.clone(), ident.into()) + }) + } + + /// IfExpression = 'if' ExpressionExceptConstructor Block ( 'else' ( Block | IfExpression ) )? + pub(super) fn parse_if_expr(&mut self) -> Option { + if !self.eat_keyword(Keyword::If) { + return None; + } + + let condition = self.parse_expression_except_constructor_or_error(); + + let start_span = self.current_token_span; + let Some(consequence) = self.parse_block() else { + self.expected_token(Token::LeftBrace); + let span = self.span_at_previous_token_end(); + return Some(ExpressionKind::If(Box::new(IfExpression { + condition, + consequence: Expression { kind: ExpressionKind::Error, span }, + alternative: None, + }))); + }; + let span = self.span_since(start_span); + let consequence = Expression { kind: ExpressionKind::Block(consequence), span }; + + let alternative = if self.eat_keyword(Keyword::Else) { + let start_span = self.current_token_span; + if let Some(block) = self.parse_block() { + let span = self.span_since(start_span); + Some(Expression { kind: ExpressionKind::Block(block), span }) + } else if let Some(if_expr) = self.parse_if_expr() { + Some(Expression { kind: if_expr, span: self.span_since(start_span) }) + } else { + self.expected_token(Token::LeftBrace); + None + } + } else { + None + }; + + Some(ExpressionKind::If(Box::new(IfExpression { condition, consequence, alternative }))) + } + + /// ComptimeExpression = 'comptime' Block + fn parse_comptime_expr(&mut self) -> Option { + if !self.eat_keyword(Keyword::Comptime) { + return None; + } + + let start_span = self.current_token_span; + + let Some(block) = self.parse_block() else { + self.expected_token(Token::LeftBrace); + return None; + }; + + Some(ExpressionKind::Comptime(block, self.span_since(start_span))) + } + + /// UnquoteExpression + /// = '$' identifier + /// | '$' '(' Expression ')' + fn parse_unquote_expr(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat(Token::DollarSign) { + return None; + } + + if let Some(path) = self.parse_path() { + let expr = Expression { + kind: ExpressionKind::Variable(path), + span: self.span_since(start_span), + }; + return Some(ExpressionKind::Unquote(Box::new(expr))); + } + + let span_at_left_paren = self.current_token_span; + if self.eat_left_paren() { + let expr = self.parse_expression_or_error(); + self.eat_or_error(Token::RightParen); + let expr = Expression { + kind: ExpressionKind::Parenthesized(Box::new(expr)), + span: self.span_since(span_at_left_paren), + }; + return Some(ExpressionKind::Unquote(Box::new(expr))); + } + + self.push_error( + ParserErrorReason::ExpectedIdentifierOrLeftParenAfterDollar, + self.current_token_span, + ); + + None + } + + /// TypePathExpression = PrimitiveType '::' identifier ( '::' GenericTypeArgs )? + fn parse_type_path_expr(&mut self) -> Option { + let start_span = self.current_token_span; + let Some(typ) = self.parse_primitive_type() else { + return None; + }; + let typ = UnresolvedType { typ, span: self.span_since(start_span) }; + + self.eat_or_error(Token::DoubleColon); + + let item = if let Some(ident) = self.eat_ident() { + ident + } else { + self.expected_identifier(); + Ident::new(String::new(), self.span_at_previous_token_end()) + }; + + let turbofish = if self.eat_double_colon() { + let generics = self.parse_generic_type_args(); + if generics.is_empty() { + self.expected_token(Token::Less); + } + generics + } else { + GenericTypeArgs::default() + }; + + Some(ExpressionKind::TypePath(TypePath { typ, item, turbofish })) + } + + /// Literal + /// = bool + /// | int + /// | str + /// | rawstr + /// | fmtstr + /// | QuoteExpression + /// | ArrayExpression + /// | SliceExpression + /// | BlockExpression + /// + /// QuoteExpression = 'quote' '{' token* '}' + /// + /// ArrayExpression = ArrayLiteral + /// + /// BlockExpression = Block + fn parse_literal(&mut self) -> Option { + if let Some(bool) = self.eat_bool() { + return Some(ExpressionKind::Literal(Literal::Bool(bool))); + } + + if let Some(int) = self.eat_int() { + return Some(ExpressionKind::integer(int)); + } + + if let Some(string) = self.eat_str() { + return Some(ExpressionKind::Literal(Literal::Str(string))); + } + + if let Some((string, n)) = self.eat_raw_str() { + return Some(ExpressionKind::Literal(Literal::RawStr(string, n))); + } + + if let Some(string) = self.eat_fmt_str() { + return Some(ExpressionKind::Literal(Literal::FmtStr(string))); + } + + if let Some(tokens) = self.eat_quote() { + return Some(ExpressionKind::Quote(tokens)); + } + + if let Some(literal) = self.parse_array_literal() { + return Some(ExpressionKind::Literal(Literal::Array(literal))); + } + + if let Some(literal) = self.parse_slice_literal() { + return Some(ExpressionKind::Literal(Literal::Slice(literal))); + } + + if let Some(kind) = self.parse_block() { + return Some(ExpressionKind::Block(kind)); + } + + None + } + + /// ArrayLiteral + /// = StandardArrayLiteral + /// | RepeatedArrayLiteral + /// + /// StandardArrayLiteral = '[' ArrayElements? ']' + /// + /// ArrayElements = Expression ( ',' Expression )? ','? + /// + /// RepeatedArrayLiteral = '[' Expression ';' TypeExpression ']' + fn parse_array_literal(&mut self) -> Option { + if !self.eat_left_bracket() { + return None; + } + + if self.eat_right_bracket() { + return Some(ArrayLiteral::Standard(Vec::new())); + } + + let first_expr = self.parse_expression_or_error(); + if first_expr.kind == ExpressionKind::Error { + return Some(ArrayLiteral::Standard(Vec::new())); + } + + if self.eat_semicolon() { + let length = self.parse_expression_or_error(); + self.eat_or_error(Token::RightBracket); + return Some(ArrayLiteral::Repeated { + repeated_element: Box::new(first_expr), + length: Box::new(length), + }); + } + + let comma_after_first_expr = self.eat_comma(); + let second_expr_span = self.current_token_span; + + let mut exprs = self.parse_many( + "expressions", + separated_by_comma().until(Token::RightBracket), + Self::parse_expression_in_list, + ); + + if !exprs.is_empty() && !comma_after_first_expr { + self.expected_token_separating_items(Token::Comma, "expressions", second_expr_span); + } + + exprs.insert(0, first_expr); + + Some(ArrayLiteral::Standard(exprs)) + } + + /// SliceExpression = '&' ArrayLiteral + fn parse_slice_literal(&mut self) -> Option { + if !(self.at(Token::Ampersand) && self.next_is(Token::LeftBracket)) { + return None; + } + + self.bump(); + self.parse_array_literal() + } + + /// ParenthesesExpression + /// = UnitLiteral + /// | ParenthesizedExpression + /// | TupleExpression + /// + /// UnitLiteral = '(' ')' + /// + /// ParenthesizedExpression = '(' Expression ')' + /// + /// TupleExpression = '(' Expression ( ',' Expression )+ ','? ')' + fn parse_parentheses_expression(&mut self) -> Option { + if !self.eat_left_paren() { + return None; + } + + if self.eat_right_paren() { + return Some(ExpressionKind::Literal(Literal::Unit)); + } + + let (mut exprs, trailing_comma) = self.parse_many_return_trailing_separator_if_any( + "expressions", + separated_by_comma_until_right_paren(), + Self::parse_expression_in_list, + ); + + Some(if exprs.len() == 1 && !trailing_comma { + ExpressionKind::Parenthesized(Box::new(exprs.remove(0))) + } else { + ExpressionKind::Tuple(exprs) + }) + } + + pub(super) fn parse_expression_in_list(&mut self) -> Option { + if let Some(expr) = self.parse_expression() { + Some(expr) + } else { + self.expected_label(ParsingRuleLabel::Expression); + None + } + } + + /// Block = '{' Statement* '}' + pub(super) fn parse_block(&mut self) -> Option { + if !self.eat_left_brace() { + return None; + } + + let statements = self.parse_many( + "statements", + without_separator().until(Token::RightBrace), + Self::parse_statement_in_block, + ); + + let statements = self.check_statements_require_semicolon(statements); + + Some(BlockExpression { statements }) + } + + fn parse_statement_in_block(&mut self) -> Option<(Statement, (Option, Span))> { + if let Some(statement) = self.parse_statement() { + Some(statement) + } else { + self.expected_label(ParsingRuleLabel::Statement); + None + } + } + + fn check_statements_require_semicolon( + &mut self, + statements: Vec<(Statement, (Option, Span))>, + ) -> Vec { + let last = statements.len().saturating_sub(1); + let iter = statements.into_iter().enumerate(); + vecmap(iter, |(i, (statement, (semicolon, span)))| { + statement + .add_semicolon(semicolon, span, i == last, &mut |error| self.errors.push(error)) + }) + } + + pub(super) fn push_expected_expression(&mut self) { + self.expected_label(ParsingRuleLabel::Expression); + } +} + +#[cfg(test)] +mod tests { + use strum::IntoEnumIterator; + + use crate::{ + ast::{ + ArrayLiteral, BinaryOpKind, Expression, ExpressionKind, Literal, StatementKind, + UnaryOp, UnresolvedTypeData, + }, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::Token, + }; + + fn parse_expression_no_errors(src: &str) -> Expression { + let mut parser = Parser::for_str(src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + expect_no_errors(&parser.errors); + expr + } + + #[test] + fn parses_bool_literals() { + let src = "true"; + let expr = parse_expression_no_errors(src); + assert!(matches!(expr.kind, ExpressionKind::Literal(Literal::Bool(true)))); + + let src = "false"; + let expr = parse_expression_no_errors(src); + assert!(matches!(expr.kind, ExpressionKind::Literal(Literal::Bool(false)))); + } + + #[test] + fn parses_integer_literal() { + let src = "42"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 42_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_negative_integer_literal() { + let src = "-42"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 42_u128.into()); + assert!(negative); + } + + #[test] + fn parses_parenthesized_expression() { + let src = "(42)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Parenthesized(expr) = expr.kind else { + panic!("Expected parenthesized expression"); + }; + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 42_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_unit() { + let src = "()"; + let expr = parse_expression_no_errors(src); + assert!(matches!(expr.kind, ExpressionKind::Literal(Literal::Unit))); + } + + #[test] + fn parses_str() { + let src = "\"hello\""; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Str(string)) = expr.kind else { + panic!("Expected string literal"); + }; + assert_eq!(string, "hello"); + } + + #[test] + fn parses_raw_str() { + let src = "r#\"hello\"#"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::RawStr(string, n)) = expr.kind else { + panic!("Expected raw string literal"); + }; + assert_eq!(string, "hello"); + assert_eq!(n, 1); + } + + #[test] + fn parses_fmt_str() { + let src = "f\"hello\""; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::FmtStr(string)) = expr.kind else { + panic!("Expected format string literal"); + }; + assert_eq!(string, "hello"); + } + + #[test] + fn parses_tuple_expression() { + let src = "(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Tuple(mut exprs) = expr.kind else { + panic!("Expected tuple expression"); + }; + assert_eq!(exprs.len(), 2); + + let expr = exprs.remove(0); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 1_u128.into()); + assert!(!negative); + + let expr = exprs.remove(0); + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 2_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_block_expression_with_a_single_expression() { + let src = "{ 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Block(mut block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 1); + + let statement = block.statements.remove(0); + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expression statement"); + }; + + let ExpressionKind::Literal(Literal::Integer(field, negative)) = expr.kind else { + panic!("Expected integer literal"); + }; + assert_eq!(field, 1_u128.into()); + assert!(!negative); + } + + #[test] + fn parses_block_expression_with_multiple_statements() { + let src = " + { + let x = 1; + let y = 2; + 3 + }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 3); + assert_eq!(block.statements[0].kind.to_string(), "let x = 1"); + assert_eq!(block.statements[1].kind.to_string(), "let y = 2"); + assert_eq!(block.statements[2].kind.to_string(), "3"); + } + + #[test] + fn parses_block_expression_adds_semicolons() { + let src = " + { + 1 + 2 + 3 + }"; + let mut parser = Parser::for_str(src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 2); + assert!(matches!( + parser.errors[0].reason(), + Some(ParserErrorReason::MissingSeparatingSemi) + )); + assert!(matches!( + parser.errors[1].reason(), + Some(ParserErrorReason::MissingSeparatingSemi) + )); + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 3); + } + + #[test] + fn parses_unsafe_expression() { + let src = "unsafe { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Unsafe(block, _) = expr.kind else { + panic!("Expected unsafe expression"); + }; + assert_eq!(block.statements.len(), 1); + } + + #[test] + fn parses_unclosed_parentheses() { + let src = " + ( + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_expression(); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected an expression but found end of input"); + } + + #[test] + fn parses_missing_comma_in_tuple() { + let src = " + (1 2) + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_expression(); + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "expressions"); + } + + #[test] + fn parses_empty_array_expression() { + let src = "[]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert!(exprs.is_empty()); + } + + #[test] + fn parses_array_expression_with_one_element() { + let src = "[1]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(exprs.len(), 1); + assert_eq!(exprs[0].to_string(), "1"); + } + + #[test] + fn parses_array_expression_with_two_elements() { + let src = "[1, 3]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(exprs.len(), 2); + assert_eq!(exprs[0].to_string(), "1"); + assert_eq!(exprs[1].to_string(), "3"); + } + + #[test] + fn parses_array_expression_with_two_elements_missing_comma() { + let src = " + [1 3] + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "expressions"); + + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(exprs.len(), 2); + assert_eq!(exprs[0].to_string(), "1"); + assert_eq!(exprs[1].to_string(), "3"); + } + + #[test] + fn parses_repeated_array_expression() { + let src = "[1; 10]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Array(ArrayLiteral::Repeated { + repeated_element, + length, + })) = expr.kind + else { + panic!("Expected array literal"); + }; + assert_eq!(repeated_element.to_string(), "1"); + assert_eq!(length.to_string(), "10"); + } + + #[test] + fn parses_empty_slice_expression() { + let src = "&[]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Literal(Literal::Slice(ArrayLiteral::Standard(exprs))) = expr.kind + else { + panic!("Expected slice literal"); + }; + assert!(exprs.is_empty()); + } + + #[test] + fn parses_variable_ident() { + let src = "foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Variable(path) = expr.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_variable_path() { + let src = "foo::bar"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Variable(path) = expr.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_variable_path_with_turbofish() { + let src = "foo::<9>"; + parse_expression_no_errors(src); + } + + #[test] + fn parses_mutable_ref() { + let src = "&mut foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::MutableReference)); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_minus() { + let src = "-foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::Minus)); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_not() { + let src = "!foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::Not)); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_dereference() { + let src = "*foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Prefix(prefix) = expr.kind else { + panic!("Expected prefix expression"); + }; + assert!(matches!(prefix.operator, UnaryOp::Dereference { implicitly_added: false })); + + let ExpressionKind::Variable(path) = prefix.rhs.kind else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "foo"); + } + + #[test] + fn parses_quote() { + let src = "quote { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Quote(tokens) = expr.kind else { + panic!("Expected quote expression"); + }; + assert_eq!(tokens.0.len(), 1); + } + + #[test] + fn parses_call() { + let src = "foo(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo"); + assert_eq!(call.arguments.len(), 2); + assert!(!call.is_macro_call); + } + + #[test] + fn parses_call_missing_comma() { + let src = " + foo(1 2) + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "arguments"); + + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo"); + assert_eq!(call.arguments.len(), 2); + assert!(!call.is_macro_call); + } + + #[test] + fn parses_call_with_wrong_expression() { + let src = "foo(]) "; + let mut parser = Parser::for_str(src); + parser.parse_expression_or_error(); + assert!(!parser.errors.is_empty()); + } + + #[test] + fn parses_call_with_turbofish() { + let src = "foo::(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo::"); + assert_eq!(call.arguments.len(), 2); + assert!(!call.is_macro_call); + } + + #[test] + fn parses_macro_call() { + let src = "foo!(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Call(call) = expr.kind else { + panic!("Expected call expression"); + }; + assert_eq!(call.func.to_string(), "foo"); + assert_eq!(call.arguments.len(), 2); + assert!(call.is_macro_call); + } + + #[test] + fn parses_member_access() { + let src = "foo.bar"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MemberAccess(member_access) = expr.kind else { + panic!("Expected member access expression"); + }; + assert_eq!(member_access.lhs.to_string(), "foo"); + assert_eq!(member_access.rhs.to_string(), "bar"); + } + + #[test] + fn parses_method_call() { + let src = "foo.bar(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MethodCall(method_call) = expr.kind else { + panic!("Expected method call expression"); + }; + assert_eq!(method_call.object.to_string(), "foo"); + assert_eq!(method_call.method_name.to_string(), "bar"); + assert!(!method_call.is_macro_call); + assert_eq!(method_call.arguments.len(), 2); + assert!(method_call.generics.is_none()); + } + + #[test] + fn parses_method_call_with_turbofish() { + let src = "foo.bar::(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MethodCall(method_call) = expr.kind else { + panic!("Expected method call expression"); + }; + assert_eq!(method_call.object.to_string(), "foo"); + assert_eq!(method_call.method_name.to_string(), "bar"); + assert!(!method_call.is_macro_call); + assert_eq!(method_call.arguments.len(), 2); + assert_eq!(method_call.generics.unwrap().len(), 2); + } + + #[test] + fn parses_method_macro_call() { + let src = "foo.bar!(1, 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::MethodCall(method_call) = expr.kind else { + panic!("Expected method call expression"); + }; + assert_eq!(method_call.object.to_string(), "foo"); + assert_eq!(method_call.method_name.to_string(), "bar"); + assert!(method_call.is_macro_call); + assert_eq!(method_call.arguments.len(), 2); + assert!(method_call.generics.is_none()); + } + + #[test] + fn parses_empty_constructor() { + let src = "Foo {}"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Constructor(constructor) = expr.kind else { + panic!("Expected constructor"); + }; + assert_eq!(constructor.typ.to_string(), "Foo"); + assert!(constructor.fields.is_empty()); + } + + #[test] + fn parses_constructor_with_fields() { + let src = "Foo { x: 1, y, z: 2 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Constructor(mut constructor) = expr.kind else { + panic!("Expected constructor"); + }; + assert_eq!(constructor.typ.to_string(), "Foo"); + assert_eq!(constructor.fields.len(), 3); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "x"); + assert_eq!(expr.to_string(), "1"); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "y"); + assert_eq!(expr.to_string(), "y"); + + let (name, expr) = constructor.fields.remove(0); + assert_eq!(name.to_string(), "z"); + assert_eq!(expr.to_string(), "2"); + } + + #[test] + fn parses_parses_if_true() { + let src = "if true { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "true"); + let ExpressionKind::Block(block_expr) = if_expr.consequence.kind else { + panic!("Expected block"); + }; + assert_eq!(block_expr.statements.len(), 1); + assert_eq!(block_expr.statements[0].kind.to_string(), "1"); + assert!(if_expr.alternative.is_none()); + } + + #[test] + fn parses_parses_if_var() { + let src = "if foo { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "foo"); + } + + #[test] + fn parses_parses_if_else() { + let src = "if true { 1 } else { 2 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "true"); + assert!(if_expr.alternative.is_some()); + } + + #[test] + fn parses_parses_if_else_if() { + let src = "if true { 1 } else if false { 2 } else { 3 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::If(if_expr) = expr.kind else { + panic!("Expected if"); + }; + assert_eq!(if_expr.condition.to_string(), "true"); + let ExpressionKind::If(..) = if_expr.alternative.unwrap().kind else { + panic!("Expected if"); + }; + } + + #[test] + fn parses_cast() { + let src = "1 as u8"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Cast(cast_expr) = expr.kind else { + panic!("Expected cast"); + }; + assert_eq!(cast_expr.lhs.to_string(), "1"); + assert_eq!(cast_expr.r#type.to_string(), "u8"); + } + + #[test] + fn parses_cast_missing_type() { + let src = " + 1 as + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_expression(); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a type but found end of input"); + } + + #[test] + fn parses_index() { + let src = "1[2]"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Index(index_expr) = expr.kind else { + panic!("Expected index"); + }; + assert_eq!(index_expr.collection.to_string(), "1"); + assert_eq!(index_expr.index.to_string(), "2"); + } + + #[test] + fn parses_operators() { + for operator in BinaryOpKind::iter() { + let src = format!("1 {operator} 2"); + let mut parser = Parser::for_str(&src); + let expr = parser.parse_expression_or_error(); + assert_eq!(expr.span.end() as usize, src.len()); + assert!(parser.errors.is_empty(), "Expected no errors for {operator}"); + let ExpressionKind::Infix(infix_expr) = expr.kind else { + panic!("Expected infix for {operator}"); + }; + assert_eq!(infix_expr.lhs.to_string(), "1"); + assert_eq!(infix_expr.operator.contents, operator); + assert_eq!(infix_expr.rhs.to_string(), "2"); + } + } + + #[test] + fn parses_operator_precedence() { + // This test produces a gigantic expression with lots of infix expressions without parentheses. + // We parse it, then we transform that to a string. Because `InfixExpression::to_string()` adds parentheses + // around it, we can check the operator precedence is correct by checking where parentheses were placed. + let multiply_or_divide_or_modulo = "1 * 2 / 3 % 4"; + let expected_multiply_or_divide_or_modulo = "(((1 * 2) / 3) % 4)"; + + let add_or_subtract = format!("{multiply_or_divide_or_modulo} + {multiply_or_divide_or_modulo} - {multiply_or_divide_or_modulo}"); + let expected_add_or_subtract = format!("(({expected_multiply_or_divide_or_modulo} + {expected_multiply_or_divide_or_modulo}) - {expected_multiply_or_divide_or_modulo})"); + + let shift = format!("{add_or_subtract} << {add_or_subtract} >> {add_or_subtract}"); + let expected_shift = format!("(({expected_add_or_subtract} << {expected_add_or_subtract}) >> {expected_add_or_subtract})"); + + let less_or_greater = format!("{shift} < {shift} > {shift} <= {shift} >= {shift}"); + let expected_less_or_greater = format!("(((({expected_shift} < {expected_shift}) > {expected_shift}) <= {expected_shift}) >= {expected_shift})"); + + let xor = format!("{less_or_greater} ^ {less_or_greater}"); + let expected_xor = format!("({expected_less_or_greater} ^ {expected_less_or_greater})"); + + let and = format!("{xor} & {xor}"); + let expected_and = format!("({expected_xor} & {expected_xor})"); + + let or = format!("{and} | {and}"); + let expected_or = format!("({expected_and} | {expected_and})"); + + let equal_or_not_equal = format!("{or} == {or} != {or}"); + let expected_equal_or_not_equal = + format!("(({expected_or} == {expected_or}) != {expected_or})"); + + let src = &equal_or_not_equal; + let expected_src = expected_equal_or_not_equal; + + let expr = parse_expression_no_errors(src); + let ExpressionKind::Infix(infix_expr) = expr.kind else { + panic!("Expected infix"); + }; + assert_eq!(infix_expr.to_string(), expected_src); + } + + #[test] + fn parses_empty_lambda() { + let src = "|| 1"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Lambda(lambda) = expr.kind else { + panic!("Expected lambda"); + }; + assert!(lambda.parameters.is_empty()); + assert_eq!(lambda.body.to_string(), "1"); + assert!(matches!(lambda.return_type.typ, UnresolvedTypeData::Unspecified)); + } + + #[test] + fn parses_lambda_with_arguments() { + let src = "|x, y: Field| 1"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Lambda(mut lambda) = expr.kind else { + panic!("Expected lambda"); + }; + assert_eq!(lambda.parameters.len(), 2); + + let (pattern, typ) = lambda.parameters.remove(0); + assert_eq!(pattern.to_string(), "x"); + assert!(matches!(typ.typ, UnresolvedTypeData::Unspecified)); + + let (pattern, typ) = lambda.parameters.remove(0); + assert_eq!(pattern.to_string(), "y"); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_lambda_with_return_type() { + let src = "|| -> Field 1"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Lambda(lambda) = expr.kind else { + panic!("Expected lambda"); + }; + assert!(lambda.parameters.is_empty()); + assert_eq!(lambda.body.to_string(), "1"); + assert!(matches!(lambda.return_type.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_as_trait_path() { + let src = "::baz"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::AsTraitPath(as_trait_path) = expr.kind else { + panic!("Expected as_trait_path") + }; + assert_eq!(as_trait_path.typ.typ.to_string(), "Field"); + assert_eq!(as_trait_path.trait_path.to_string(), "foo::Bar"); + assert!(as_trait_path.trait_generics.is_empty()); + assert_eq!(as_trait_path.impl_item.to_string(), "baz"); + } + + #[test] + fn parses_comptime_expression() { + let src = "comptime { 1 }"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Comptime(block, _) = expr.kind else { + panic!("Expected comptime block"); + }; + assert_eq!(block.statements.len(), 1); + } + + #[test] + fn parses_type_path() { + let src = "Field::foo"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::TypePath(type_path) = expr.kind else { + panic!("Expected type_path"); + }; + assert_eq!(type_path.typ.to_string(), "Field"); + assert_eq!(type_path.item.to_string(), "foo"); + assert!(type_path.turbofish.is_empty()); + } + + #[test] + fn parses_type_path_with_generics() { + let src = "Field::foo::"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::TypePath(type_path) = expr.kind else { + panic!("Expected type_path"); + }; + assert_eq!(type_path.typ.to_string(), "Field"); + assert_eq!(type_path.item.to_string(), "foo"); + assert!(!type_path.turbofish.is_empty()); + } + + #[test] + fn parses_unquote_var() { + let src = "$foo::bar"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Unquote(expr) = expr.kind else { + panic!("Expected unquote"); + }; + let ExpressionKind::Variable(path) = expr.kind else { + panic!("Expected unquote"); + }; + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_unquote_expr() { + let src = "$(1 + 2)"; + let expr = parse_expression_no_errors(src); + let ExpressionKind::Unquote(expr) = expr.kind else { + panic!("Expected unquote"); + }; + assert_eq!(expr.kind.to_string(), "((1 + 2))"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs index 7b1f67a48bd..6d1fc611767 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/function.rs @@ -1,319 +1,483 @@ -use super::{ - attributes::{attributes, validate_attributes}, - block, fresh_statement, ident, keyword, maybe_comp_time, nothing, parameter_name_recovery, - parameter_recovery, parenthesized, parse_type, pattern, - primitives::token_kind, - self_parameter, - visibility::{item_visibility, visibility}, - where_clause, NoirParser, -}; -use crate::token::{Keyword, Token, TokenKind}; -use crate::{ - ast::{BlockExpression, IntegerBitSize}, - parser::spanned, +use crate::ast::{ + BlockExpression, GenericTypeArgs, Ident, Path, Pattern, UnresolvedTraitConstraint, + UnresolvedType, }; +use crate::token::{Attribute, Attributes, Keyword, Token}; +use crate::{ast::UnresolvedGenerics, parser::labels::ParsingRuleLabel}; use crate::{ ast::{ FunctionDefinition, FunctionReturnType, ItemVisibility, NoirFunction, Param, UnresolvedTypeData, Visibility, }, - parser::{ParserError, ParserErrorReason}, -}; -use crate::{ - ast::{Signedness, UnresolvedGeneric, UnresolvedGenerics}, - parser::labels::ParsingRuleLabel, + parser::ParserErrorReason, }; +use acvm::AcirField; -use chumsky::prelude::*; use noirc_errors::Span; -/// function_definition: attribute function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block -/// function_modifiers 'fn' ident generics '(' function_parameters ')' function_return_type block -pub(super) fn function_definition(allow_self: bool) -> impl NoirParser { - let body_or_error = - spanned(block(fresh_statement()).or_not()).validate(|(body, body_span), span, emit| { - if let Some(body) = body { - (body, body_span) - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBraceOrArrowAfterFunctionParameters, - span, - )); - (BlockExpression { statements: vec![] }, Span::from(span.end()..span.end())) - } - }); - - attributes() - .then(function_modifiers()) - .then_ignore(keyword(Keyword::Fn)) - .then(ident()) - .then(generics()) - .then( - parenthesized(function_parameters(allow_self)) - .then(function_return_type()) - .then(where_clause()) - .then(body_or_error) - // Allow parsing just `fn foo` for recovery and LSP autocompletion - .or_not(), +use super::parse_many::separated_by_comma_until_right_paren; +use super::pattern::SelfPattern; +use super::{pattern::PatternOrSelf, Parser}; + +pub(crate) struct FunctionDefinitionWithOptionalBody { + pub(crate) name: Ident, + pub(crate) generics: UnresolvedGenerics, + pub(crate) parameters: Vec, + pub(crate) body: Option, + pub(crate) span: Span, + pub(crate) where_clause: Vec, + pub(crate) return_type: FunctionReturnType, + pub(crate) return_visibility: Visibility, +} + +impl<'a> Parser<'a> { + /// Function = 'fn' identifier Generics FunctionParameters ( '->' Visibility Type )? WhereClause ( Block | ';' ) + pub(crate) fn parse_function( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + is_comptime: bool, + is_unconstrained: bool, + allow_self: bool, + ) -> NoirFunction { + self.parse_function_definition( + attributes, + visibility, + is_comptime, + is_unconstrained, + allow_self, ) - .validate(|args, span, emit| { - let ( - (((attributes, (is_unconstrained, visibility, is_comptime)), name), generics), - params_and_others, - ) = args; - - // Validate collected attributes, filtering them into function and secondary variants - let attributes = validate_attributes(attributes, span, emit); - - let function_definition = if let Some(params_and_others) = params_and_others { - let ( - ((parameters, (return_visibility, return_type)), where_clause), - (body, body_span), - ) = params_and_others; - - FunctionDefinition { - span: body_span, - name, - attributes, - is_unconstrained, - visibility, - is_comptime, - generics, - parameters, - body, - where_clause, - return_type, - return_visibility, - } + .into() + } + + pub(crate) fn parse_function_definition( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + is_comptime: bool, + is_unconstrained: bool, + allow_self: bool, + ) -> FunctionDefinition { + let attributes = self.validate_attributes(attributes); + + let func = self.parse_function_definition_with_optional_body( + false, // allow optional body + allow_self, + ); + + FunctionDefinition { + name: func.name, + attributes, + is_unconstrained, + is_comptime, + visibility, + generics: func.generics, + parameters: func.parameters, + body: func.body.unwrap_or_else(empty_body), + span: func.span, + where_clause: func.where_clause, + return_type: func.return_type, + return_visibility: func.return_visibility, + } + } + + pub(super) fn parse_function_definition_with_optional_body( + &mut self, + allow_optional_body: bool, + allow_self: bool, + ) -> FunctionDefinitionWithOptionalBody { + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return empty_function(self.previous_token_span); + }; + + let generics = self.parse_generics(); + let parameters = self.parse_function_parameters(allow_self); + + let (return_type, return_visibility) = if self.eat(Token::Arrow) { + let visibility = self.parse_visibility(); + (FunctionReturnType::Ty(self.parse_type_or_error()), visibility) + } else { + (FunctionReturnType::Default(self.span_at_previous_token_end()), Visibility::Private) + }; + + let where_clause = self.parse_where_clause(); + + let body_start_span = self.current_token_span; + let body = if self.eat_semicolons() { + if !allow_optional_body { + self.push_error(ParserErrorReason::ExpectedFunctionBody, body_start_span); + } + + None + } else { + Some(self.parse_block().unwrap_or_else(empty_body)) + }; + + FunctionDefinitionWithOptionalBody { + name, + generics, + parameters, + body, + span: self.span_since(body_start_span), + where_clause, + return_type, + return_visibility, + } + } + + /// FunctionParameters = '(' FunctionParametersList? ')' + /// + /// FunctionParametersList = FunctionParameter ( ',' FunctionParameter )* ','? + /// + /// FunctionParameter = Visibility PatternOrSelf ':' Type + fn parse_function_parameters(&mut self, allow_self: bool) -> Vec { + if !self.eat_left_paren() { + return Vec::new(); + } + + self.parse_many("parameters", separated_by_comma_until_right_paren(), |parser| { + parser.parse_function_parameter(allow_self) + }) + } + + fn parse_function_parameter(&mut self, allow_self: bool) -> Option { + loop { + let start_span = self.current_token_span; + + let pattern_or_self = if allow_self { + self.parse_pattern_or_self() } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftParenOrLeftBracketAfterFunctionName, - span, - )); - - let empty_span = Span::from(span.end()..span.end()); - FunctionDefinition { - span: empty_span, - name, - attributes, - is_unconstrained, - visibility, - is_comptime, - generics, - parameters: Vec::new(), - body: BlockExpression { statements: vec![] }, - where_clause: Vec::new(), - return_type: FunctionReturnType::Default(empty_span), - return_visibility: Visibility::Private, + self.parse_pattern().map(PatternOrSelf::Pattern) + }; + + let Some(pattern_or_self) = pattern_or_self else { + self.expected_label(ParsingRuleLabel::Pattern); + + // Let's try with the next token + self.bump(); + if self.at_eof() { + return None; + } else { + continue; } }; - function_definition.into() - }) -} -/// function_modifiers: 'unconstrained'? (visibility)? -/// -/// returns (is_unconstrained, visibility) for whether each keyword was present -pub(super) fn function_modifiers() -> impl NoirParser<(bool, ItemVisibility, bool)> { - keyword(Keyword::Unconstrained).or_not().then(item_visibility()).then(maybe_comp_time()).map( - |((unconstrained, visibility), comptime)| (unconstrained.is_some(), visibility, comptime), - ) -} + return Some(match pattern_or_self { + PatternOrSelf::Pattern(pattern) => self.pattern_param(pattern, start_span), + PatternOrSelf::SelfPattern(self_pattern) => self.self_pattern_param(self_pattern), + }); + } + } -pub(super) fn numeric_generic() -> impl NoirParser { - keyword(Keyword::Let) - .ignore_then(ident()) - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .map(|(ident, typ)| UnresolvedGeneric::Numeric { ident, typ }) - .validate(|generic, span, emit| { - if let UnresolvedGeneric::Numeric { typ, .. } = &generic { - if let UnresolvedTypeData::Integer(signedness, bit_size) = typ.typ { - if matches!(signedness, Signedness::Signed) - || matches!(bit_size, IntegerBitSize::SixtyFour) - { - emit(ParserError::with_reason( - ParserErrorReason::ForbiddenNumericGenericType, - span, - )); - } + fn pattern_param(&mut self, pattern: Pattern, start_span: Span) -> Param { + let (visibility, typ) = if !self.eat_colon() { + self.push_error( + ParserErrorReason::MissingTypeForFunctionParameter, + Span::from(pattern.span().start()..self.current_token_span.end()), + ); + + let visibility = Visibility::Private; + let typ = UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }; + (visibility, typ) + } else { + (self.parse_visibility(), self.parse_type_or_error()) + }; + + Param { visibility, pattern, typ, span: self.span_since(start_span) } + } + + fn self_pattern_param(&mut self, self_pattern: SelfPattern) -> Param { + let ident_span = self.previous_token_span; + let ident = Ident::new("self".to_string(), ident_span); + let path = Path::from_single("Self".to_owned(), ident_span); + let no_args = GenericTypeArgs::default(); + let mut self_type = UnresolvedTypeData::Named(path, no_args, true).with_span(ident_span); + let mut pattern = Pattern::Identifier(ident); + + if self_pattern.reference { + self_type = + UnresolvedTypeData::MutableReference(Box::new(self_type)).with_span(ident_span); + } else if self_pattern.mutable { + pattern = Pattern::Mutable(Box::new(pattern), ident_span, true); + } + + Param { + visibility: Visibility::Private, + pattern, + typ: self_type, + span: self.span_since(ident_span), + } + } + + /// Visibility + /// = 'pub' + /// | 'return_data' + /// | 'call_data' '(' int ')' + /// | nothing + fn parse_visibility(&mut self) -> Visibility { + if self.eat_keyword(Keyword::Pub) { + return Visibility::Public; + } + + if self.eat_keyword(Keyword::ReturnData) { + return Visibility::ReturnData; + } + + if self.eat_keyword(Keyword::CallData) { + if self.eat_left_paren() { + if let Some(int) = self.eat_int() { + self.eat_or_error(Token::RightParen); + + let id = int.to_u128() as u32; + return Visibility::CallData(id); + } else { + self.expected_label(ParsingRuleLabel::Integer); + self.eat_right_paren(); + return Visibility::CallData(0); } + } else { + self.expected_token(Token::LeftParen); + return Visibility::CallData(0); } - generic - }) -} + } -pub(super) fn generic_type() -> impl NoirParser { - ident().map(UnresolvedGeneric::Variable) -} + Visibility::Private + } + + fn validate_attributes(&mut self, attributes: Vec<(Attribute, Span)>) -> Attributes { + let mut primary = None; + let mut secondary = Vec::new(); + + for (attribute, span) in attributes { + match attribute { + Attribute::Function(attr) => { + if primary.is_some() { + self.push_error(ParserErrorReason::MultipleFunctionAttributesFound, span); + } + primary = Some(attr); + } + Attribute::Secondary(attr) => secondary.push(attr), + } + } -pub(super) fn resolved_generic() -> impl NoirParser { - token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { - Token::QuotedType(id) => UnresolvedGeneric::Resolved(id, span), - _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), - }) + Attributes { function: primary, secondary } + } } -pub(super) fn generic() -> impl NoirParser { - generic_type().or(numeric_generic()).or(resolved_generic()) +fn empty_function(span: Span) -> FunctionDefinitionWithOptionalBody { + FunctionDefinitionWithOptionalBody { + name: Ident::default(), + generics: Vec::new(), + parameters: Vec::new(), + body: None, + span: Span::from(span.end()..span.end()), + where_clause: Vec::new(), + return_type: FunctionReturnType::Default(Span::default()), + return_visibility: Visibility::Private, + } } -/// non_empty_ident_list: ident ',' non_empty_ident_list -/// | ident -/// -/// generics: '<' non_empty_ident_list '>' -/// | %empty -pub(super) fn generics() -> impl NoirParser { - generic() - .separated_by(just(Token::Comma)) - .allow_trailing() - .delimited_by(just(Token::Less), just(Token::Greater)) - .or_not() - .map(|opt| opt.unwrap_or_default()) +fn empty_body() -> BlockExpression { + BlockExpression { statements: Vec::new() } } -pub(super) fn function_return_type() -> impl NoirParser<(Visibility, FunctionReturnType)> { - #[allow(deprecated)] - just(Token::Arrow).ignore_then(visibility()).then(spanned(parse_type())).or_not().map_with_span( - |ret, span| match ret { - Some((visibility, (ty, _))) => (visibility, FunctionReturnType::Ty(ty)), - None => (Visibility::Private, FunctionReturnType::Default(span)), +#[cfg(test)] +mod tests { + use crate::{ + ast::{NoirFunction, UnresolvedTypeData, Visibility}, + parser::{ + parser::{ + parse_program, + tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + }, + ItemKind, ParserErrorReason, }, - ) -} + }; -fn function_parameters<'a>(allow_self: bool) -> impl NoirParser> + 'a { - let typ = parse_type().recover_via(parameter_recovery()); + fn parse_function_no_error(src: &str) -> NoirFunction { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Function(noir_function) = item.kind else { + panic!("Expected function"); + }; + noir_function + } - let full_parameter = pattern() - .recover_via(parameter_name_recovery()) - .then_ignore(just(Token::Colon)) - .then(visibility()) - .then(typ) - .map_with_span(|((pattern, visibility), typ), span| Param { - visibility, - pattern, - typ, - span, - }); + #[test] + fn parse_simple_function() { + let src = "fn foo() {}"; + let noir_function = parse_function_no_error(src); + assert_eq!("foo", noir_function.def.name.to_string()); + assert!(noir_function.def.parameters.is_empty()); + assert!(noir_function.def.generics.is_empty()); + } - let self_parameter = if allow_self { self_parameter().boxed() } else { nothing().boxed() }; + #[test] + fn parse_function_with_generics() { + let src = "fn foo() {}"; + let noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.generics.len(), 1); + } - let parameter = full_parameter.or(self_parameter); + #[test] + fn parse_function_with_arguments() { + let src = "fn foo(x: Field, y: Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 2); - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} + let param = noir_function.def.parameters.remove(0); + assert_eq!("x", param.pattern.to_string()); + assert_eq!("Field", param.typ.to_string()); + assert_eq!(param.visibility, Visibility::Private); -#[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; + let param = noir_function.def.parameters.remove(0); + assert_eq!("y", param.pattern.to_string()); + assert_eq!("Field", param.typ.to_string()); + assert_eq!(param.visibility, Visibility::Private); + } #[test] - fn regression_skip_comment() { - parse_all( - function_definition(false), - vec![ - "fn main( - // This comment should be skipped - x : Field, - // And this one - y : Field, - ) { - }", - "fn main(x : Field, y : Field,) { - foo::bar( - // Comment for x argument - x, - // Comment for y argument - y - ) - }", - ], - ); + fn parse_function_with_argument_pub_visibility() { + let src = "fn foo(x: pub Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 1); + + let param = noir_function.def.parameters.remove(0); + assert_eq!("x", param.pattern.to_string()); + assert_eq!("Field", param.typ.to_string()); + assert_eq!(param.visibility, Visibility::Public); } #[test] - fn parse_function() { - parse_all( - function_definition(false), - vec![ - "fn func_name() {}", - "fn f(foo: pub u8, y : pub Field) -> u8 { x + a }", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) {}", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", - "fn func_name(x: [Field], y : [Field;2],y : pub [Field;2], z : pub [u8;5]) {}", - "fn main(x: pub u8, y: pub u8) -> pub [u8; 2] { [x, y] }", - "fn f(f: pub Field, y : Field, z : Field) -> u8 { x + a }", - "fn f(f: pub Field, y : T, z : Field) -> u8 { x + a }", - "fn func_name(f: Field, y : T) where T: SomeTrait {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait, T: SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 {}", - "fn func_name(f: Field, y : T) where T: SomeTrait + SomeTrait2 + TraitY {}", - "fn func_name(f: Field, y : T, z : U) where SomeStruct: SomeTrait {}", - // 'where u32: SomeTrait' is allowed in Rust. - // It will result in compiler error in case SomeTrait isn't implemented for u32. - "fn func_name(f: Field, y : T) where u32: SomeTrait {}", - // A trailing plus is allowed by Rust, so we support it as well. - "fn func_name(f: Field, y : T) where T: SomeTrait + {}", - // The following should produce compile error on later stage. From the parser's perspective it's fine - "fn func_name(f: Field, y : Field, z : Field) where T: SomeTrait {}", - // TODO: this fails with known EOF != EOF error - // https://github.com/noir-lang/noir/issues/4763 - // fn func_name(x: impl Eq) {} with error Expected an end of input but found end of input - // "fn func_name(x: impl Eq) {}", - "fn func_name(x: impl Eq, y : T) where T: SomeTrait + Eq {}", - "fn func_name(x: [Field; N]) {}", - ], - ); + fn parse_function_with_argument_return_data_visibility() { + let src = "fn foo(x: return_data Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 1); - parse_all_failing( - function_definition(false), - vec![ - "fn x2( f: []Field,,) {}", - "fn ( f: []Field) {}", - "fn ( f: []Field) {}", - // TODO: Check for more specific error messages - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where T: {}", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) where SomeTrait {}", - "fn func_name(f: Field, y : pub Field, z : pub [u8;5],) SomeTrait {}", - // A leading plus is not allowed. - "fn func_name(f: Field, y : T) where T: + SomeTrait {}", - "fn func_name(f: Field, y : T) where T: TraitX + {}", - // Test ill-formed numeric generics - "fn func_name(y: T) {}", - "fn func_name(y: T) {}", - "fn func_name(y: T) {}", - // Test failure of missing `let` - "fn func_name(y: T) {}", - // Test that signed numeric generics are banned - "fn func_name() {}", - // Test that `u64` is banned - "fn func_name(x: [Field; N]) {}", - ], - ); + let param = noir_function.def.parameters.remove(0); + assert_eq!(param.visibility, Visibility::ReturnData); + } + + #[test] + fn parse_function_with_argument_call_data_visibility() { + let src = "fn foo(x: call_data(42) Field) {}"; + let mut noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.parameters.len(), 1); + + let param = noir_function.def.parameters.remove(0); + assert_eq!(param.visibility, Visibility::CallData(42)); + } + + #[test] + fn parse_function_return_type() { + let src = "fn foo() -> Field {}"; + let noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.return_visibility, Visibility::Private); + assert_eq!(noir_function.return_type().typ, UnresolvedTypeData::FieldElement); } #[test] - fn parse_recover_function_without_body() { - let src = "fn foo(x: i32)"; + fn parse_function_return_visibility() { + let src = "fn foo() -> pub Field {}"; + let noir_function = parse_function_no_error(src); + assert_eq!(noir_function.def.return_visibility, Visibility::Public); + assert_eq!(noir_function.return_type().typ, UnresolvedTypeData::FieldElement); + } - let (noir_function, errors) = parse_recover(function_definition(false), src); + #[test] + fn parse_function_unclosed_parentheses() { + let src = "fn foo(x: i32,"; + let (module, errors) = parse_program(src); assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected { or -> after function parameters"); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Function(noir_function) = &item.kind else { + panic!("Expected function"); + }; + assert_eq!("foo", noir_function.def.name.to_string()); + } + + #[test] + fn parse_error_multiple_function_attributes_found() { + let src = " + #[foreign(foo)] #[oracle(bar)] fn foo() {} + ^^^^^^^^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::MultipleFunctionAttributesFound)); + } - let noir_function = noir_function.unwrap(); - assert_eq!(noir_function.name(), "foo"); + #[test] + fn parse_function_found_semicolon_instead_of_braces() { + let src = " + fn foo(); + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::ExpectedFunctionBody)); + } + + #[test] + fn recovers_on_wrong_parameter_name() { + let src = " + fn foo(1 x: i32) {} + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let ItemKind::Function(noir_function) = &module.items[0].kind else { + panic!("Expected function"); + }; assert_eq!(noir_function.parameters().len(), 1); - assert!(noir_function.def.body.statements.is_empty()); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a pattern but found 1"); + } + + #[test] + fn recovers_on_missing_colon_after_parameter_name() { + let src = " + fn foo(x, y: i32) {} + ^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let ItemKind::Function(noir_function) = &module.items[0].kind else { + panic!("Expected function"); + }; + assert_eq!(noir_function.parameters().len(), 2); + + let error = get_single_error(&errors, span); + assert!(error.to_string().contains("Missing type for function parameter")); + } + + #[test] + fn recovers_on_missing_type_after_parameter_colon() { + let src = " + fn foo(x: , y: i32) {} + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let ItemKind::Function(noir_function) = &module.items[0].kind else { + panic!("Expected function"); + }; + assert_eq!(noir_function.parameters().len(), 2); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a type but found ,"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/generics.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/generics.rs new file mode 100644 index 00000000000..2c8ba5a2a65 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/generics.rs @@ -0,0 +1,283 @@ +use crate::{ + ast::{ + GenericTypeArg, GenericTypeArgs, IntegerBitSize, Signedness, UnresolvedGeneric, + UnresolvedGenerics, UnresolvedType, UnresolvedTypeData, + }, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{parse_many::separated_by_comma, Parser}; + +impl<'a> Parser<'a> { + /// Generics = ( '<' GenericsList? '>' )? + /// + /// GenericsList = Generic ( ',' Generic )* ','? + pub(super) fn parse_generics(&mut self) -> UnresolvedGenerics { + if !self.eat_less() { + return Vec::new(); + } + + self.parse_many( + "generic parameters", + separated_by_comma().until(Token::Greater), + Self::parse_generic_in_list, + ) + } + + fn parse_generic_in_list(&mut self) -> Option { + if let Some(generic) = self.parse_generic() { + Some(generic) + } else { + self.expected_label(ParsingRuleLabel::GenericParameter); + None + } + } + + /// Generic + /// = VariableGeneric + /// | NumericGeneric + /// | ResolvedGeneric + fn parse_generic(&mut self) -> Option { + if let Some(generic) = self.parse_variable_generic() { + return Some(generic); + } + + if let Some(generic) = self.parse_numeric_generic() { + return Some(generic); + } + + if let Some(generic) = self.parse_resolved_generic() { + return Some(generic); + } + + None + } + + /// VariableGeneric = identifier + fn parse_variable_generic(&mut self) -> Option { + self.eat_ident().map(UnresolvedGeneric::Variable) + } + + /// NumericGeneric = 'let' identifier ':' Type + fn parse_numeric_generic(&mut self) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let ident = self.eat_ident()?; + + if !self.eat_colon() { + // If we didn't get a type after the colon, error and assume it's u32 + self.push_error( + ParserErrorReason::MissingTypeForNumericGeneric, + self.current_token_span, + ); + let typ = UnresolvedType { + typ: UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo), + span: self.span_at_previous_token_end(), + }; + return Some(UnresolvedGeneric::Numeric { ident, typ }); + } + + let typ = self.parse_type_or_error(); + if let UnresolvedTypeData::Integer(signedness, bit_size) = &typ.typ { + if matches!(signedness, Signedness::Signed) + || matches!(bit_size, IntegerBitSize::SixtyFour) + { + self.push_error(ParserErrorReason::ForbiddenNumericGenericType, typ.span); + } + } + + Some(UnresolvedGeneric::Numeric { ident, typ }) + } + + /// ResolvedGeneric = quoted_type + fn parse_resolved_generic(&mut self) -> Option { + let token = self.eat_kind(TokenKind::QuotedType)?; + match token.into_token() { + Token::QuotedType(id) => { + Some(UnresolvedGeneric::Resolved(id, self.previous_token_span)) + } + _ => unreachable!(), + } + } + + /// GenericTypeArgs = ( '<' GenericTypeArgsList? '>' ) + /// + /// GenericTypeArgsList = GenericTypeArg ( ',' GenericTypeArg )* ','? + /// + /// GenericTypeArg + /// = NamedTypeArg + /// | OrderedTypeArg + /// + /// NamedTypeArg = identifier '=' Type + /// + /// OrderedTypeArg = TypeOrTypeExpression + pub(super) fn parse_generic_type_args(&mut self) -> GenericTypeArgs { + let mut generic_type_args = GenericTypeArgs::default(); + if !self.eat_less() { + return generic_type_args; + } + + let generics = self.parse_many( + "generic parameters", + separated_by_comma().until(Token::Greater), + Self::parse_generic_type_arg, + ); + + for generic in generics { + match generic { + GenericTypeArg::Ordered(typ) => { + generic_type_args.ordered_args.push(typ); + } + GenericTypeArg::Named(name, typ) => { + generic_type_args.named_args.push((name, typ)); + } + } + } + + generic_type_args + } + + fn parse_generic_type_arg(&mut self) -> Option { + if matches!(self.token.token(), Token::Ident(..)) && self.next_is(Token::Assign) { + let ident = self.eat_ident().unwrap(); + + self.eat_assign(); + + let typ = self.parse_type_or_error(); + return Some(GenericTypeArg::Named(ident, typ)); + } + + // Otherwise + let Some(typ) = self.parse_type_or_type_expression() else { + self.expected_label(ParsingRuleLabel::TypeOrTypeExpression); + return None; + }; + + Some(GenericTypeArg::Ordered(typ)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{GenericTypeArgs, IntegerBitSize, Signedness, UnresolvedGeneric, UnresolvedTypeData}, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + }; + + fn parse_generics_no_errors(src: &str) -> Vec { + let mut parser = Parser::for_str(src); + let generics = parser.parse_generics(); + expect_no_errors(&parser.errors); + generics + } + + fn parse_generic_type_args_no_errors(src: &str) -> GenericTypeArgs { + let mut parser = Parser::for_str(src); + let generics = parser.parse_generic_type_args(); + expect_no_errors(&parser.errors); + generics + } + + #[test] + fn parses_no_generics() { + let src = "1"; + let generics = parse_generics_no_errors(src); + assert!(generics.is_empty()); + } + + #[test] + fn parses_generics() { + let src = ""; + let mut generics = parse_generics_no_errors(src); + assert_eq!(generics.len(), 2); + + let generic = generics.remove(0); + let UnresolvedGeneric::Variable(ident) = generic else { + panic!("Expected generic variable"); + }; + assert_eq!("A", ident.to_string()); + + let generic = generics.remove(0); + let UnresolvedGeneric::Numeric { ident, typ } = generic else { + panic!("Expected generic numeric"); + }; + assert_eq!("B", ident.to_string()); + assert_eq!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + ); + } + + #[test] + fn parses_no_generic_type_args() { + let src = "1"; + let generics = parse_generic_type_args_no_errors(src); + assert!(generics.is_empty()); + } + + #[test] + fn parses_generic_type_args() { + let src = ""; + let generics = parse_generic_type_args_no_errors(src); + assert!(!generics.is_empty()); + assert_eq!(generics.ordered_args.len(), 1); + assert_eq!(generics.ordered_args[0].to_string(), "i32"); + assert_eq!(generics.named_args.len(), 1); + assert_eq!(generics.named_args[0].0.to_string(), "X"); + assert_eq!(generics.named_args[0].1.to_string(), "Field"); + } + + #[test] + fn parses_generic_type_arg_that_is_a_path() { + let src = ""; + let generics = parse_generic_type_args_no_errors(src); + assert!(!generics.is_empty()); + assert_eq!(generics.ordered_args.len(), 1); + assert_eq!(generics.ordered_args[0].to_string(), "foo::Bar"); + assert_eq!(generics.named_args.len(), 0); + } + + #[test] + fn parses_generic_type_arg_that_is_an_int() { + let src = "<1>"; + let generics = parse_generic_type_args_no_errors(src); + assert!(!generics.is_empty()); + assert_eq!(generics.ordered_args.len(), 1); + assert_eq!(generics.ordered_args[0].to_string(), "1"); + } + + #[test] + fn parse_numeric_generic_error_if_invalid_integer() { + let src = " + + ^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_generics(); + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::ForbiddenNumericGenericType)); + } + + #[test] + fn parse_arithmetic_generic_on_variable() { + let src = ""; + let generics = parse_generic_type_args_no_errors(src); + assert_eq!(generics.ordered_args[0].to_string(), "(N - 1)"); + } + + #[test] + fn parse_var_with_turbofish_in_generic() { + let src = ">"; + let generics = parse_generic_type_args_no_errors(src); + assert_eq!(generics.ordered_args[0].to_string(), "N<1>"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs new file mode 100644 index 00000000000..2ea6457dc0b --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/global.rs @@ -0,0 +1,168 @@ +use noirc_errors::Span; + +use crate::{ + ast::{ + Expression, ExpressionKind, Ident, LetStatement, Pattern, UnresolvedType, + UnresolvedTypeData, + }, + parser::ParserErrorReason, + token::{Attribute, Token}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// Global = 'global' identifier OptionalTypeAnnotation '=' Expression ';' + pub(crate) fn parse_global( + &mut self, + attributes: Vec<(Attribute, Span)>, + comptime: bool, + mutable: bool, + ) -> LetStatement { + // Only comptime globals are allowed to be mutable, but we always parse the `mut` + // and throw the error in name resolution. + + let attributes = self.validate_secondary_attributes(attributes); + + let Some(ident) = self.eat_ident() else { + self.eat_semicolons(); + return LetStatement { + pattern: ident_to_pattern(Ident::default(), mutable), + r#type: UnresolvedType { + typ: UnresolvedTypeData::Unspecified, + span: Span::default(), + }, + expression: Expression { kind: ExpressionKind::Error, span: Span::default() }, + attributes, + comptime, + }; + }; + + let pattern = ident_to_pattern(ident, mutable); + + let typ = self.parse_optional_type_annotation(); + + let expression = if self.eat_assign() { + self.parse_expression_or_error() + } else { + self.push_error(ParserErrorReason::GlobalWithoutValue, pattern.span()); + Expression { kind: ExpressionKind::Error, span: Span::default() } + }; + + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + + LetStatement { pattern, r#type: typ, expression, attributes, comptime } + } +} + +fn ident_to_pattern(ident: Ident, mutable: bool) -> Pattern { + if mutable { + Pattern::Mutable(Box::new(Pattern::Identifier(ident)), Span::default(), false) + } else { + Pattern::Identifier(ident) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ + IntegerBitSize, ItemVisibility, LetStatement, Pattern, Signedness, UnresolvedTypeData, + }, + parser::{ + parser::{ + parse_program, + tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + }, + ItemKind, ParserErrorReason, + }, + }; + + fn parse_global_no_errors(src: &str) -> (LetStatement, ItemVisibility) { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Global(let_statement, visibility) = item.kind else { + panic!("Expected global"); + }; + (let_statement, visibility) + } + + #[test] + fn parse_global_no_type_annotation() { + let src = "global foo = 1;"; + let (let_statement, visibility) = parse_global_no_errors(src); + let Pattern::Identifier(name) = &let_statement.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!("foo", name.to_string()); + assert!(matches!(let_statement.r#type.typ, UnresolvedTypeData::Unspecified)); + assert!(!let_statement.comptime); + assert_eq!(visibility, ItemVisibility::Private); + } + + #[test] + fn parse_global_with_type_annotation() { + let src = "global foo: i32 = 1;"; + let (let_statement, _visibility) = parse_global_no_errors(src); + let Pattern::Identifier(name) = &let_statement.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!("foo", name.to_string()); + assert!(matches!( + let_statement.r#type.typ, + UnresolvedTypeData::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + )); + } + + #[test] + fn parse_comptime_global() { + let src = "comptime global foo: i32 = 1;"; + let (let_statement, _visibility) = parse_global_no_errors(src); + assert!(let_statement.comptime); + } + + #[test] + fn parse_mutable_global() { + let src = "mut global foo: i32 = 1;"; + let (let_statement, _visibility) = parse_global_no_errors(src); + let Pattern::Mutable(pattern, _, _) = &let_statement.pattern else { + panic!("Expected mutable pattern"); + }; + let pattern: &Pattern = pattern; + let Pattern::Identifier(name) = pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!("foo", name.to_string()); + } + + #[test] + fn parse_global_no_value() { + let src = " + global foo; + ^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::GlobalWithoutValue)); + } + + #[test] + fn parse_global_no_semicolon() { + let src = " + global foo = 1 + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a ; but found end of input"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs new file mode 100644 index 00000000000..460c7bded15 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/impls.rs @@ -0,0 +1,567 @@ +use noirc_errors::Span; + +use crate::{ + ast::{ + Documented, Expression, ExpressionKind, GenericTypeArgs, Ident, ItemVisibility, + NoirFunction, NoirTraitImpl, Path, TraitImplItem, TraitImplItemKind, TypeImpl, + UnresolvedGeneric, UnresolvedType, UnresolvedTypeData, + }, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{parse_many::without_separator, Parser}; + +pub(crate) enum Impl { + Impl(TypeImpl), + TraitImpl(NoirTraitImpl), +} + +impl<'a> Parser<'a> { + /// Impl + /// = TypeImpl + /// | TraitImpl + pub(crate) fn parse_impl(&mut self) -> Impl { + let generics = self.parse_generics(); + + let type_span_start = self.current_token_span; + let object_type = self.parse_type_or_error(); + let type_span = self.span_since(type_span_start); + + if self.eat_keyword(Keyword::For) { + if let UnresolvedTypeData::Named(trait_name, trait_generics, _) = object_type.typ { + return Impl::TraitImpl(self.parse_trait_impl( + generics, + trait_generics, + trait_name, + )); + } else { + self.push_error( + ParserErrorReason::ExpectedTrait { found: object_type.typ.to_string() }, + self.current_token_span, + ); + + // Error, but we continue parsing the type and assume this is going to be a regular type impl + self.parse_type(); + }; + } + + self.parse_type_impl(object_type, type_span, generics) + } + + /// TypeImpl = 'impl' Generics Type TypeImplBody + fn parse_type_impl( + &mut self, + object_type: UnresolvedType, + type_span: Span, + generics: Vec, + ) -> Impl { + let where_clause = self.parse_where_clause(); + let methods = self.parse_type_impl_body(); + + Impl::Impl(TypeImpl { object_type, type_span, generics, where_clause, methods }) + } + + /// TypeImplBody = '{' TypeImplItem* '}' + /// + /// TypeImplItem = OuterDocComments Attributes Modifiers Function + fn parse_type_impl_body(&mut self) -> Vec<(Documented, Span)> { + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return Vec::new(); + } + + self.parse_many( + "type impl methods", + without_separator().until(Token::RightBrace), + Self::parse_type_impl_method, + ) + } + + fn parse_type_impl_method(&mut self) -> Option<(Documented, Span)> { + self.parse_item_in_list( + ParsingRuleLabel::TokenKind(TokenKind::Token(Token::Keyword(Keyword::Fn))), + |parser| { + let doc_comments = parser.parse_outer_doc_comments(); + let start_span = parser.current_token_span; + let attributes = parser.parse_attributes(); + let modifiers = parser.parse_modifiers( + false, // allow mutable + ); + + if parser.eat_keyword(Keyword::Fn) { + let method = parser.parse_function( + attributes, + modifiers.visibility, + modifiers.comptime.is_some(), + modifiers.unconstrained.is_some(), + true, // allow_self + ); + Some((Documented::new(method, doc_comments), parser.span_since(start_span))) + } else { + parser.modifiers_not_followed_by_an_item(modifiers); + None + } + }, + ) + } + + /// TraitImpl = 'impl' Generics Path GenericTypeArgs 'for' Type TraitImplBody + fn parse_trait_impl( + &mut self, + impl_generics: Vec, + trait_generics: GenericTypeArgs, + trait_name: Path, + ) -> NoirTraitImpl { + let object_type = self.parse_type_or_error(); + let where_clause = self.parse_where_clause(); + let items = self.parse_trait_impl_body(); + + NoirTraitImpl { + impl_generics, + trait_name, + trait_generics, + object_type, + where_clause, + items, + } + } + + /// TraitImplBody = '{' TraitImplItem* '}' + fn parse_trait_impl_body(&mut self) -> Vec> { + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return Vec::new(); + } + + self.parse_many( + "trait impl item", + without_separator().until(Token::RightBrace), + Self::parse_trait_impl_item, + ) + } + + fn parse_trait_impl_item(&mut self) -> Option> { + self.parse_item_in_list(ParsingRuleLabel::TraitImplItem, |parser| { + let start_span = parser.current_token_span; + let doc_comments = parser.parse_outer_doc_comments(); + + if let Some(kind) = parser.parse_trait_impl_item_kind() { + let item = TraitImplItem { kind, span: parser.span_since(start_span) }; + Some(Documented::new(item, doc_comments)) + } else { + None + } + }) + } + + /// TraitImplItem + /// = TraitImplType + /// | TraitImplConstant + /// | TraitImplFunction + fn parse_trait_impl_item_kind(&mut self) -> Option { + if let Some(kind) = self.parse_trait_impl_type() { + return Some(kind); + } + + if let Some(kind) = self.parse_trait_impl_constant() { + return Some(kind); + } + + self.parse_trait_impl_function() + } + + /// TraitImplType = 'type' identifier ( ':' Type )? ';' + fn parse_trait_impl_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::Type) { + return None; + } + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + self.eat_semicolons(); + return Some(TraitImplItemKind::Type { + name: Ident::default(), + alias: UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }, + }); + }; + + let alias = if self.eat_assign() { + self.parse_type_or_error() + } else { + UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() } + }; + + self.eat_semicolons(); + + Some(TraitImplItemKind::Type { name, alias }) + } + + /// TraitImplConstant = 'let' identifier OptionalTypeAnnotation ';' + fn parse_trait_impl_constant(&mut self) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let name = match self.eat_ident() { + Some(name) => name, + None => { + self.expected_identifier(); + Ident::default() + } + }; + + let typ = self.parse_optional_type_annotation(); + + let expr = if self.eat_assign() { + self.parse_expression_or_error() + } else { + self.expected_token(Token::Assign); + Expression { kind: ExpressionKind::Error, span: Span::default() } + }; + + self.eat_semicolons(); + + Some(TraitImplItemKind::Constant(name, typ, expr)) + } + + /// TraitImplFunction = Attributes Modifiers Function + fn parse_trait_impl_function(&mut self) -> Option { + let attributes = self.parse_attributes(); + + let modifiers = self.parse_modifiers( + false, // allow mut + ); + if modifiers.visibility != ItemVisibility::Private { + self.push_error( + ParserErrorReason::TraitImplVisibilityIgnored, + modifiers.visibility_span, + ); + } + + if !self.eat_keyword(Keyword::Fn) { + self.modifiers_not_followed_by_an_item(modifiers); + return None; + } + + let noir_function = self.parse_function( + attributes, + ItemVisibility::Public, + modifiers.comptime.is_some(), + modifiers.unconstrained.is_some(), + true, // allow_self + ); + Some(TraitImplItemKind::Function(noir_function)) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ + ItemVisibility, NoirTraitImpl, Pattern, TraitImplItemKind, TypeImpl, UnresolvedTypeData, + }, + parser::{ + parser::{ + parse_program, + tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + }, + ItemKind, + }, + }; + + fn parse_type_impl_no_errors(src: &str) -> TypeImpl { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Impl(type_impl) = item.kind else { + panic!("Expected type impl"); + }; + type_impl + } + + fn parse_trait_impl_no_errors(src: &str) -> NoirTraitImpl { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::TraitImpl(noir_trait_impl) = item.kind else { + panic!("Expected trait impl"); + }; + noir_trait_impl + } + + #[test] + fn parse_empty_impl() { + let src = "impl Foo {}"; + let type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert!(type_impl.generics.is_empty()); + assert!(type_impl.methods.is_empty()); + } + + #[test] + fn parse_empty_impl_with_generics() { + let src = "impl Foo {}"; + let type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert_eq!(type_impl.generics.len(), 2); + assert!(type_impl.methods.is_empty()); + } + + #[test] + fn parse_impl_with_methods() { + let src = "impl Foo { unconstrained fn foo() {} pub comptime fn bar() {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert_eq!(type_impl.methods.len(), 2); + + let (method, _) = type_impl.methods.remove(0); + let method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert!(method.def.is_unconstrained); + assert!(!method.def.is_comptime); + assert_eq!(method.def.visibility, ItemVisibility::Private); + + let (method, _) = type_impl.methods.remove(0); + let method = method.item; + assert_eq!(method.def.name.to_string(), "bar"); + assert!(method.def.is_comptime); + assert_eq!(method.def.visibility, ItemVisibility::Public); + } + + #[test] + fn parse_impl_with_attribute_on_method() { + let src = " + impl Foo { + #[something] + fn foo(self) {} + } + "; + let type_impl = parse_type_impl_no_errors(src); + let attributes = type_impl.methods[0].0.item.attributes(); + assert_eq!(attributes.secondary.len(), 1); + } + + #[test] + fn parse_impl_with_self_argument() { + let src = "impl Foo { fn foo(self) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Identifier(name) = param.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "Self"); + } + + #[test] + fn parse_impl_with_mut_self_argument() { + let src = "impl Foo { fn foo(mut self) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Mutable(pattern, _, true) = param.pattern else { + panic!("Expected mutable pattern"); + }; + let pattern: &Pattern = &pattern; + let Pattern::Identifier(name) = pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "Self"); + } + + #[test] + fn parse_impl_with_reference_mut_self_argument() { + let src = "impl Foo { fn foo(&mut self) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Identifier(name) = param.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "&mut Self"); + } + + #[test] + fn parse_impl_with_self_argument_followed_by_type() { + let src = "impl Foo { fn foo(self: Foo) {} }"; + let mut type_impl = parse_type_impl_no_errors(src); + assert_eq!(type_impl.methods.len(), 1); + + let (method, _) = type_impl.methods.remove(0); + let mut method = method.item; + assert_eq!(method.def.name.to_string(), "foo"); + assert_eq!(method.def.parameters.len(), 1); + + let param = method.def.parameters.remove(0); + let Pattern::Identifier(name) = param.pattern else { + panic!("Expected identifier pattern"); + }; + assert_eq!(name.to_string(), "self"); + assert_eq!(param.typ.to_string(), "Foo"); + } + + #[test] + fn parse_empty_impl_missing_right_brace() { + let src = "impl Foo {"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 1); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Impl(type_impl) = &item.kind else { + panic!("Expected type impl"); + }; + assert_eq!(type_impl.object_type.to_string(), "Foo"); + } + + #[test] + fn parse_empty_impl_incorrect_body() { + let src = "impl Foo { hello fn foo() {} }"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 1); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Impl(type_impl) = &item.kind else { + panic!("Expected type impl"); + }; + assert_eq!(type_impl.object_type.to_string(), "Foo"); + assert_eq!(type_impl.methods.len(), 1); + } + + #[test] + fn parse_empty_trait_impl() { + let src = "impl Foo for Field {}"; + let trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert!(matches!(trait_impl.object_type.typ, UnresolvedTypeData::FieldElement)); + assert!(trait_impl.items.is_empty()); + assert!(trait_impl.impl_generics.is_empty()); + } + + #[test] + fn parse_empty_trait_impl_with_generics() { + let src = "impl Foo for Field {}"; + let trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert!(matches!(trait_impl.object_type.typ, UnresolvedTypeData::FieldElement)); + assert!(trait_impl.items.is_empty()); + assert_eq!(trait_impl.impl_generics.len(), 1); + } + + #[test] + fn parse_trait_impl_with_function() { + let src = "impl Foo for Field { fn foo() {} }"; + let mut trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(trait_impl.items.len(), 1); + + let item = trait_impl.items.remove(0).item; + let TraitImplItemKind::Function(function) = item.kind else { + panic!("Expected function"); + }; + assert_eq!(function.def.name.to_string(), "foo"); + assert_eq!(function.def.visibility, ItemVisibility::Public); + } + + #[test] + fn parse_trait_impl_with_generic_type_args() { + let src = "impl Foo for Field { }"; + let trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert!(!trait_impl.trait_generics.is_empty()); + } + + #[test] + fn parse_trait_impl_with_type() { + let src = "impl Foo for Field { type Foo = i32; }"; + let mut trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(trait_impl.items.len(), 1); + + let item = trait_impl.items.remove(0).item; + let TraitImplItemKind::Type { name, alias } = item.kind else { + panic!("Expected type"); + }; + assert_eq!(name.to_string(), "Foo"); + assert_eq!(alias.to_string(), "i32"); + } + + #[test] + fn parse_trait_impl_with_let() { + let src = "impl Foo for Field { let x: Field = 1; }"; + let mut trait_impl = parse_trait_impl_no_errors(src); + assert_eq!(trait_impl.trait_name.to_string(), "Foo"); + assert_eq!(trait_impl.items.len(), 1); + + let item = trait_impl.items.remove(0).item; + let TraitImplItemKind::Constant(name, typ, expr) = item.kind else { + panic!("Expected constant"); + }; + assert_eq!(name.to_string(), "x"); + assert_eq!(typ.to_string(), "Field"); + assert_eq!(expr.to_string(), "1"); + } + + #[test] + fn recovers_on_unknown_impl_item() { + let src = " + impl Foo { hello fn foo() {} } + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Impl(type_impl) = &item.kind else { + panic!("Expected impl"); + }; + assert_eq!(type_impl.methods.len(), 1); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a fn but found hello"); + } + + #[test] + fn recovers_on_unknown_trait_impl_item() { + let src = " + impl Foo for i32 { hello fn foo() {} } + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::TraitImpl(trait_imp) = &item.kind else { + panic!("Expected trait impl"); + }; + assert_eq!(trait_imp.items.len(), 1); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a trait impl item but found hello"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/infix.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/infix.rs new file mode 100644 index 00000000000..f006923b8a2 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/infix.rs @@ -0,0 +1,193 @@ +use noirc_errors::{Span, Spanned}; + +use crate::{ + ast::{BinaryOpKind, Expression, ExpressionKind, InfixExpression}, + token::Token, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// EqualOrNotEqualExpression + /// = OrExpression ( ( '==' | '!=' ) OrExpression )* + pub(super) fn parse_equal_or_not_equal( + &mut self, + allow_constructors: bool, + ) -> Option { + self.parse_infix(allow_constructors, Parser::parse_or, |parser| { + if parser.eat(Token::Equal) { + Some(BinaryOpKind::Equal) + } else if parser.eat(Token::NotEqual) { + Some(BinaryOpKind::NotEqual) + } else { + None + } + }) + } + + /// OrExpression + /// = AndExpression ( '|' AndExpression )* + pub(super) fn parse_or(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_and, |parser| { + // Don't parse `x |= ...`, etc. + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Pipe) { + Some(BinaryOpKind::Or) + } else { + None + } + }) + } + + /// AndExpression + /// = XorExpression ( '&' XorExpression )* + pub(super) fn parse_and(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_xor, |parser| { + // Don't parse `x |= ...`, etc. + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Ampersand) { + Some(BinaryOpKind::And) + } else { + None + } + }) + } + + /// XorExpression + /// = LessOrGreaterExpression ( '^' LessOrGreaterExpression )* + pub(super) fn parse_xor(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_less_or_greater, |parser| { + // Don't parse `x |= ...`, etc. + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Caret) { + Some(BinaryOpKind::Xor) + } else { + None + } + }) + } + + /// LessOrGreaterExpression + /// = ShiftExpression ( ( '<' | '<=' | '>' | '>=' ) ShiftExpression )* + pub(super) fn parse_less_or_greater(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_shift, |parser| { + if parser.eat(Token::Less) { + Some(BinaryOpKind::Less) + } else if parser.eat(Token::LessEqual) { + Some(BinaryOpKind::LessEqual) + } else if parser.next_token.token() != &Token::GreaterEqual + && parser.eat(Token::Greater) + { + // Make sure to skip the `>>=` case, as `>>=` is lexed as `> >=`. + Some(BinaryOpKind::Greater) + } else if parser.eat(Token::GreaterEqual) { + Some(BinaryOpKind::GreaterEqual) + } else { + None + } + }) + } + + /// ShiftExpression + /// = AddOrSubtractExpression ( ( '<<' | '>' '>' ) AddOrSubtractExpression )* + pub(super) fn parse_shift(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_add_or_subtract, |parser| { + if !parser.next_is(Token::Assign) && parser.eat(Token::ShiftLeft) { + Some(BinaryOpKind::ShiftLeft) + } else if parser.at(Token::Greater) && parser.next_is(Token::Greater) { + // Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier + // to parse nested generic types. For normal expressions however, it means we have to manually + // parse two greater-than tokens as a single right-shift here. + parser.bump(); + parser.bump(); + Some(BinaryOpKind::ShiftRight) + } else { + None + } + }) + } + + /// AddOrSubtractExpression + /// = MultiplyOrDivideOrModuloExpression ( ( '+' | '-' ) MultiplyOrDivideOrModuloExpression )* + pub(super) fn parse_add_or_subtract(&mut self, allow_constructors: bool) -> Option { + self.parse_infix(allow_constructors, Parser::parse_multiply_or_divide_or_modulo, |parser| { + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Plus) { + Some(BinaryOpKind::Add) + } else if parser.eat(Token::Minus) { + Some(BinaryOpKind::Subtract) + } else { + None + } + }) + } + + /// MultiplyOrDivideOrModuloExpression + /// = Term ( ( '*' | '/' | '%' ) Term )* + pub(super) fn parse_multiply_or_divide_or_modulo( + &mut self, + allow_constructors: bool, + ) -> Option { + self.parse_infix(allow_constructors, Parser::parse_term, |parser| { + if parser.next_is(Token::Assign) { + None + } else if parser.eat(Token::Star) { + Some(BinaryOpKind::Multiply) + } else if parser.eat(Token::Slash) { + Some(BinaryOpKind::Divide) + } else if parser.eat(Token::Percent) { + Some(BinaryOpKind::Modulo) + } else { + None + } + }) + } + + fn parse_infix( + &mut self, + allow_constructors: bool, + mut next: Next, + mut op: Op, + ) -> Option + where + Next: FnMut(&mut Parser<'a>, bool) -> Option, + Op: FnMut(&mut Parser<'a>) -> Option, + { + let start_span = self.current_token_span; + let mut lhs = next(self, allow_constructors)?; + + loop { + let operator_start_span = self.current_token_span; + let Some(operator) = op(self) else { + break; + }; + let operator = Spanned::from(operator_start_span, operator); + + let Some(rhs) = next(self, allow_constructors) else { + self.push_expected_expression(); + break; + }; + + lhs = self.new_infix_expression(lhs, operator, rhs, start_span); + } + + Some(lhs) + } + + fn new_infix_expression( + &self, + lhs: Expression, + operator: Spanned, + rhs: Expression, + start_span: Span, + ) -> Expression { + let infix_expr = InfixExpression { lhs, operator, rhs }; + let kind = ExpressionKind::Infix(Box::new(infix_expr)); + let span = self.span_since(start_span); + Expression { kind, span } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs new file mode 100644 index 00000000000..0faa2ba80ee --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item.rs @@ -0,0 +1,242 @@ +use crate::{ + parser::{labels::ParsingRuleLabel, Item, ItemKind}, + token::{Keyword, Token}, +}; + +use super::{impls::Impl, parse_many::without_separator, Parser}; + +impl<'a> Parser<'a> { + pub(crate) fn parse_top_level_items(&mut self) -> Vec { + self.parse_module_items( + false, // nested + ) + } + + pub(crate) fn parse_module_items(&mut self, nested: bool) -> Vec { + self.parse_many("items", without_separator(), |parser| { + parser.parse_module_item_in_list(nested) + }) + } + + fn parse_module_item_in_list(&mut self, nested: bool) -> Option { + loop { + // We only break out of the loop on `}` if we are inside a `mod { ..` + if nested && self.at(Token::RightBrace) { + return None; + } + + // We always break on EOF (we don't error because if we are inside `mod { ..` + // the outer parsing logic will error instead) + if self.at_eof() { + return None; + } + + let Some(item) = self.parse_item() else { + // If we couldn't parse an item we check which token we got + match self.token.token() { + Token::RightBrace if nested => { + return None; + } + Token::EOF => return None, + _ => (), + } + + self.expected_label(ParsingRuleLabel::Item); + // We'll try parsing an item starting on the next token + self.bump(); + continue; + }; + + return Some(item); + } + } + + /// Parses an item inside an impl or trait, with good recovery: + /// - If we run into EOF, we error that we expect a '}' + /// - If we can't parse an item and we don't end up in '}', error but try with the next token + pub(super) fn parse_item_in_list( + &mut self, + label: ParsingRuleLabel, + mut f: F, + ) -> Option + where + F: FnMut(&mut Parser<'a>) -> Option, + { + loop { + if self.at_eof() { + self.expected_token(Token::RightBrace); + return None; + } + + let Some(item) = f(self) else { + if !self.at(Token::RightBrace) { + self.expected_label(label.clone()); + + // Try with the next token + self.bump(); + continue; + } + + return None; + }; + + return Some(item); + } + } + + /// Item = OuterDocComments ItemKind + fn parse_item(&mut self) -> Option { + let start_span = self.current_token_span; + let doc_comments = self.parse_outer_doc_comments(); + let kind = self.parse_item_kind()?; + let span = self.span_since(start_span); + + Some(Item { kind, span, doc_comments }) + } + + /// ItemKind + /// = InnerAttribute + /// | Attributes Modifiers + /// ( Use + /// | ModOrContract + /// | Struct + /// | Impl + /// | Trait + /// | Global + /// | TypeAlias + /// | Function + /// ) + fn parse_item_kind(&mut self) -> Option { + if let Some(kind) = self.parse_inner_attribute() { + return Some(ItemKind::InnerAttribute(kind)); + } + + let start_span = self.current_token_span; + let attributes = self.parse_attributes(); + + let modifiers = self.parse_modifiers( + true, // allow mut + ); + + if self.eat_keyword(Keyword::Use) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + let use_tree = self.parse_use_tree(); + return Some(ItemKind::Import(use_tree, modifiers.visibility)); + } + + if let Some(is_contract) = self.eat_mod_or_contract() { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(self.parse_mod_or_contract(attributes, is_contract, modifiers.visibility)); + } + + if self.eat_keyword(Keyword::Struct) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(ItemKind::Struct(self.parse_struct( + attributes, + modifiers.visibility, + start_span, + ))); + } + + if self.eat_keyword(Keyword::Impl) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(match self.parse_impl() { + Impl::Impl(type_impl) => ItemKind::Impl(type_impl), + Impl::TraitImpl(noir_trait_impl) => ItemKind::TraitImpl(noir_trait_impl), + }); + } + + if self.eat_keyword(Keyword::Trait) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(ItemKind::Trait(self.parse_trait( + attributes, + modifiers.visibility, + start_span, + ))); + } + + if self.eat_keyword(Keyword::Global) { + self.unconstrained_not_applicable(modifiers); + + return Some(ItemKind::Global( + self.parse_global( + attributes, + modifiers.comptime.is_some(), + modifiers.mutable.is_some(), + ), + modifiers.visibility, + )); + } + + if self.eat_keyword(Keyword::Type) { + self.comptime_mutable_and_unconstrained_not_applicable(modifiers); + + return Some(ItemKind::TypeAlias( + self.parse_type_alias(modifiers.visibility, start_span), + )); + } + + if self.eat_keyword(Keyword::Fn) { + self.mutable_not_applicable(modifiers); + + return Some(ItemKind::Function(self.parse_function( + attributes, + modifiers.visibility, + modifiers.comptime.is_some(), + modifiers.unconstrained.is_some(), + false, // allow_self + ))); + } + + None + } + + fn eat_mod_or_contract(&mut self) -> Option { + if self.eat_keyword(Keyword::Mod) { + Some(false) + } else if self.eat_keyword(Keyword::Contract) { + Some(true) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + parse_program, + parser::parser::tests::{get_single_error, get_source_with_error_span}, + }; + + #[test] + fn recovers_on_unknown_item() { + let src = " + fn foo() {} hello fn bar() {} + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 2); + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected an item but found hello"); + } + + #[test] + fn errors_on_eof_in_nested_mod() { + let src = " + mod foo { fn foo() {} + ^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + assert_eq!(module.items.len(), 1); + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected a } but found end of input"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs new file mode 100644 index 00000000000..1731284e354 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/item_visibility.rs @@ -0,0 +1,114 @@ +use crate::{ + ast::ItemVisibility, + token::{Keyword, Token}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// ItemVisibility + /// = 'pub' // ItemVisibility::Public + /// | 'pub' '(' 'crate' ')' // ItemVisibility::PublicCrate + /// | nothing // ItemVisibility::Private + pub(super) fn parse_item_visibility(&mut self) -> ItemVisibility { + if !self.eat_keyword(Keyword::Pub) { + return ItemVisibility::Private; + } + + if !self.eat_left_paren() { + // `pub` + return ItemVisibility::Public; + } + + if !self.eat_keyword(Keyword::Crate) { + // `pub(` or `pub()` + self.expected_token(Token::Keyword(Keyword::Crate)); + self.eat_right_paren(); + return ItemVisibility::Public; + } + + self.eat_or_error(Token::RightParen); + + // `pub(crate)`` + ItemVisibility::PublicCrate + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::ItemVisibility, + parser::{ + parser::tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + Parser, + }, + }; + + #[test] + fn parses_private_visibility() { + let src = "("; + let mut parser = Parser::for_str(src); + let visibility = parser.parse_item_visibility(); + expect_no_errors(&parser.errors); + assert_eq!(visibility, ItemVisibility::Private); + } + + #[test] + fn parses_public_visibility() { + let src = "pub"; + let mut parser = Parser::for_str(src); + let visibility = parser.parse_item_visibility(); + expect_no_errors(&parser.errors); + assert_eq!(visibility, ItemVisibility::Public); + } + + #[test] + fn parses_public_visibility_unclosed_parentheses() { + let src = " + pub( + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let visibility = parser.parse_item_visibility(); + assert_eq!(visibility, ItemVisibility::Public); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a crate but found end of input"); + } + + #[test] + fn parses_public_visibility_no_crate_after_pub() { + let src = " + pub(hello + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let visibility = parser.parse_item_visibility(); + assert_eq!(visibility, ItemVisibility::Public); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a crate but found hello"); + } + #[test] + fn parses_public_visibility_missing_paren_after_pub_crate() { + let src = " + pub(crate + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let visibility = parser.parse_item_visibility(); + assert_eq!(visibility, ItemVisibility::PublicCrate); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a ) but found end of input"); + } + + #[test] + fn parses_public_crate_visibility() { + let src = "pub(crate)"; + let mut parser = Parser::for_str(src); + let visibility = parser.parse_item_visibility(); + expect_no_errors(&parser.errors); + assert_eq!(visibility, ItemVisibility::PublicCrate); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambda.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambda.rs new file mode 100644 index 00000000000..a6eeb428621 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambda.rs @@ -0,0 +1,58 @@ +use crate::{ + ast::{ExpressionKind, Lambda, Pattern, UnresolvedType}, + parser::labels::ParsingRuleLabel, + token::Token, +}; + +use super::{parse_many::separated_by_comma, Parser}; + +impl<'a> Parser<'a> { + /// Lambda = '|' LambdaParameters? '|' ( '->' Type )? Expression + /// + /// LambdaParameters = LambdaParameter ( ',' LambdaParameter )? ','? + /// + /// LambdaParameter + /// = Pattern OptionalTypeAnnotation + pub(super) fn parse_lambda(&mut self) -> Option { + if !self.eat_pipe() { + return None; + } + + let parameters = self.parse_lambda_parameters(); + let return_type = if self.eat(Token::Arrow) { + self.parse_type_or_error() + } else { + self.unspecified_type_at_previous_token_end() + }; + let body = self.parse_expression_or_error(); + + Some(ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body }))) + } + + fn parse_lambda_parameters(&mut self) -> Vec<(Pattern, UnresolvedType)> { + self.parse_many( + "parameters", + separated_by_comma().until(Token::Pipe), + Self::parse_lambda_parameter, + ) + } + + fn parse_lambda_parameter(&mut self) -> Option<(Pattern, UnresolvedType)> { + loop { + let Some(pattern) = self.parse_pattern() else { + self.expected_label(ParsingRuleLabel::Pattern); + + // Let's try with the next token. + self.bump(); + if self.at_eof() { + return None; + } else { + continue; + } + }; + + let typ = self.parse_optional_type_annotation(); + return Some((pattern, typ)); + } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs deleted file mode 100644 index 68b5724edc6..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/lambdas.rs +++ /dev/null @@ -1,42 +0,0 @@ -use chumsky::{primitive::just, Parser}; - -use super::{parse_type, pattern}; -use crate::ast::{Expression, ExpressionKind, Lambda, Pattern, UnresolvedType, UnresolvedTypeData}; -use crate::{ - parser::{labels::ParsingRuleLabel, parameter_name_recovery, parameter_recovery, NoirParser}, - token::Token, -}; - -pub(super) fn lambda<'a>( - expr_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - lambda_parameters() - .delimited_by(just(Token::Pipe), just(Token::Pipe)) - .then(lambda_return_type()) - .then(expr_parser) - .map(|((parameters, return_type), body)| { - ExpressionKind::Lambda(Box::new(Lambda { parameters, return_type, body })) - }) -} - -fn lambda_parameters() -> impl NoirParser> { - let typ = parse_type().recover_via(parameter_recovery()); - let typ = just(Token::Colon).ignore_then(typ); - - let parameter = - pattern().recover_via(parameter_name_recovery()).then(typ.or_not().map_with_span( - |typ, span| typ.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span)), - )); - - parameter - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled(ParsingRuleLabel::Parameter) -} - -fn lambda_return_type() -> impl NoirParser { - just(Token::Arrow) - .ignore_then(parse_type()) - .or_not() - .map_with_span(|ret, span| ret.unwrap_or(UnresolvedTypeData::Unspecified.with_span(span))) -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs deleted file mode 100644 index b25b6acc9e2..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/literals.rs +++ /dev/null @@ -1,158 +0,0 @@ -use chumsky::Parser; - -use crate::{ - ast::ExpressionKind, - parser::NoirParser, - token::{Token, TokenKind}, -}; - -use super::primitives::token_kind; - -pub(super) fn literal() -> impl NoirParser { - token_kind(TokenKind::Literal).map(|token| match token { - Token::Int(x) => ExpressionKind::integer(x), - Token::Bool(b) => ExpressionKind::boolean(b), - Token::Str(s) => ExpressionKind::string(s), - Token::RawStr(s, hashes) => ExpressionKind::raw_string(s, hashes), - Token::FmtStr(s) => ExpressionKind::format_string(s), - unexpected => unreachable!("Non-literal {} parsed as a literal", unexpected), - }) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::ast::Literal; - use crate::parser::parser::{ - expression, expression_no_constructors, fresh_statement, term, test_helpers::*, - }; - - fn expr_to_lit(expr: ExpressionKind) -> Literal { - match expr { - ExpressionKind::Literal(literal) => literal, - _ => unreachable!("expected a literal"), - } - } - - #[test] - fn parse_int() { - let int = parse_with(literal(), "5").unwrap(); - let hex = parse_with(literal(), "0x05").unwrap(); - - match (expr_to_lit(int), expr_to_lit(hex)) { - (Literal::Integer(int, false), Literal::Integer(hex, false)) => assert_eq!(int, hex), - _ => unreachable!(), - } - } - - #[test] - fn parse_string() { - let expr = parse_with(literal(), r#""hello""#).unwrap(); - match expr_to_lit(expr) { - Literal::Str(s) => assert_eq!(s, "hello"), - _ => unreachable!(), - }; - } - - #[test] - fn parse_bool() { - let expr_true = parse_with(literal(), "true").unwrap(); - let expr_false = parse_with(literal(), "false").unwrap(); - - match (expr_to_lit(expr_true), expr_to_lit(expr_false)) { - (Literal::Bool(t), Literal::Bool(f)) => { - assert!(t); - assert!(!f); - } - _ => unreachable!(), - }; - } - - #[test] - fn parse_unary() { - parse_all( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], - ); - parse_all_failing( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["+hello", "/hello"], - ); - } - - #[test] - fn parse_raw_string_expr() { - let cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // mismatch: short: - Case { source: r##" r"foo"# "##, expect: r#"r"foo""#, errors: 1 }, - Case { source: r#" r#"foo" "#, expect: "(none)", errors: 2 }, - // empty string - Case { source: r#"r"""#, expect: r#"r"""#, errors: 0 }, - #[allow(clippy::needless_raw_string_hashes)] - Case { source: r####"r###""###"####, expect: r####"r###""###"####, errors: 0 }, - // miscellaneous - Case { source: r##" r#\"foo\"# "##, expect: "r", errors: 2 }, - Case { source: r#" r\"foo\" "#, expect: "r", errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - // missing 'r' letter - Case { source: r##" ##"foo"# "##, expect: r#""foo""#, errors: 2 }, - Case { source: r#" #"foo" "#, expect: "foo", errors: 2 }, - // whitespace - Case { source: r##" r #"foo"# "##, expect: "r", errors: 2 }, - Case { source: r##" r# "foo"# "##, expect: "r", errors: 3 }, - Case { source: r#" r#"foo" # "#, expect: "(none)", errors: 2 }, - // after identifier - Case { source: r##" bar#"foo"# "##, expect: "bar", errors: 2 }, - // nested - Case { - source: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - expect: r###"r##"foo r#"bar"# r"baz" ### bye"##"###, - errors: 0, - }, - ]; - - check_cases_with_errors(&cases[..], expression()); - } - - #[test] - fn parse_raw_string_lit() { - let lit_cases = vec![ - Case { source: r#" r"foo" "#, expect: r#"r"foo""#, errors: 0 }, - Case { source: r##" r#"foo"# "##, expect: r##"r#"foo"#"##, errors: 0 }, - // backslash - Case { source: r#" r"\\" "#, expect: r#"r"\\""#, errors: 0 }, - Case { source: r##" r#"\"# "##, expect: r##"r#"\"#"##, errors: 0 }, - Case { source: r##" r#"\\"# "##, expect: r##"r#"\\"#"##, errors: 0 }, - Case { source: r##" r#"\\\"# "##, expect: r##"r#"\\\"#"##, errors: 0 }, - // escape sequence - Case { - source: r##" r#"\t\n\\t\\n\\\t\\\n\\\\"# "##, - expect: r##"r#"\t\n\\t\\n\\\t\\\n\\\\"#"##, - errors: 0, - }, - Case { source: r##" r#"\\\\\\\\"# "##, expect: r##"r#"\\\\\\\\"#"##, errors: 0 }, - // mismatch - errors: - Case { source: r###" r#"foo"## "###, expect: r##"r#"foo"#"##, errors: 1 }, - Case { source: r##" r##"foo"# "##, expect: "(none)", errors: 2 }, - ]; - - check_cases_with_errors(&lit_cases[..], literal()); - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/modifiers.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/modifiers.rs new file mode 100644 index 00000000000..d3bf692ee53 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/modifiers.rs @@ -0,0 +1,39 @@ +use noirc_errors::Span; + +use crate::{ast::ItemVisibility, token::Keyword}; + +use super::Parser; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub(crate) struct Modifiers { + pub(crate) visibility: ItemVisibility, + pub(crate) visibility_span: Span, + pub(crate) unconstrained: Option, + pub(crate) comptime: Option, + pub(crate) mutable: Option, +} + +impl<'a> Parser<'a> { + /// Modifiers = 'unconstrained'? ItemVisibility 'comptime'? 'mut'? + pub(crate) fn parse_modifiers(&mut self, allow_mutable: bool) -> Modifiers { + let unconstrained = if self.eat_keyword(Keyword::Unconstrained) { + Some(self.previous_token_span) + } else { + None + }; + + let start_span = self.current_token_span; + let visibility = self.parse_item_visibility(); + let visibility_span = self.span_since(start_span); + + let comptime = + if self.eat_keyword(Keyword::Comptime) { Some(self.previous_token_span) } else { None }; + let mutable = if allow_mutable && self.eat_keyword(Keyword::Mut) { + Some(self.previous_token_span) + } else { + None + }; + + Modifiers { visibility, visibility_span, unconstrained, comptime, mutable } + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/module.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/module.rs new file mode 100644 index 00000000000..263338863c0 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/module.rs @@ -0,0 +1,104 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, ItemVisibility, ModuleDeclaration}, + parser::{ItemKind, ParsedSubModule}, + token::{Attribute, Token}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// ModOrContract + /// = ( 'mod' | 'contract' ) identifier ( '{' Module '}' | ';' ) + pub(super) fn parse_mod_or_contract( + &mut self, + attributes: Vec<(Attribute, Span)>, + is_contract: bool, + visibility: ItemVisibility, + ) -> ItemKind { + let outer_attributes = self.validate_secondary_attributes(attributes); + + let Some(ident) = self.eat_ident() else { + self.expected_identifier(); + return ItemKind::ModuleDecl(ModuleDeclaration { + visibility, + ident: Ident::default(), + outer_attributes, + }); + }; + + if self.eat_left_brace() { + let contents = self.parse_module( + true, // nested + ); + self.eat_or_error(Token::RightBrace); + ItemKind::Submodules(ParsedSubModule { + visibility, + name: ident, + contents, + outer_attributes, + is_contract, + }) + } else { + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + ItemKind::ModuleDecl(ModuleDeclaration { visibility, ident, outer_attributes }) + } + } +} + +#[cfg(test)] +mod tests { + use crate::parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }; + + #[test] + fn parse_module_declaration() { + // TODO: `contract foo;` is parsed correctly but we don't it's considered a module + let src = "mod foo;"; + let (module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::ModuleDecl(module) = &item.kind else { + panic!("Expected module declaration"); + }; + assert_eq!("foo", module.ident.to_string()); + } + + #[test] + fn parse_submodule() { + let src = "mod foo { mod bar; }"; + let (module, errors) = parse_program(src); + dbg!(&errors); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Submodules(parsed_submodule) = &item.kind else { + panic!("Expected submodules declaration"); + }; + assert!(!parsed_submodule.is_contract); + assert_eq!("foo", parsed_submodule.name.to_string()); + assert_eq!(parsed_submodule.contents.items.len(), 1); + } + + #[test] + fn parse_contract() { + let src = "contract foo {}"; + let (module, errors) = parse_program(src); + dbg!(&errors); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Submodules(parsed_submodule) = &item.kind else { + panic!("Expected submodules declaration"); + }; + assert!(parsed_submodule.is_contract); + assert_eq!("foo", parsed_submodule.name.to_string()); + assert_eq!(parsed_submodule.contents.items.len(), 0); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/parse_many.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/parse_many.rs new file mode 100644 index 00000000000..ea4dfe97122 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/parse_many.rs @@ -0,0 +1,108 @@ +use crate::token::Token; + +use super::Parser; + +impl<'a> Parser<'a> { + /// Parses a list of items separated by a token, optionally ending when another token is found. + /// The given function `f` must parse one item (eventually parsing many, if separators are found). + /// If no item is parsed, `f` must report an error and return `None`. + pub(super) fn parse_many( + &mut self, + items: &'static str, + separated_by: SeparatedBy, + f: F, + ) -> Vec + where + F: FnMut(&mut Parser<'a>) -> Option, + { + self.parse_many_return_trailing_separator_if_any(items, separated_by, f).0 + } + + /// Same as parse_many, but returns a bool indicating whether a trailing separator was found. + pub(super) fn parse_many_return_trailing_separator_if_any( + &mut self, + items: &'static str, + separated_by: SeparatedBy, + mut f: F, + ) -> (Vec, bool) + where + F: FnMut(&mut Parser<'a>) -> Option, + { + let mut elements: Vec = Vec::new(); + let mut trailing_separator = false; + loop { + if let Some(end) = &separated_by.until { + if self.eat(end.clone()) { + break; + } + } + + let start_span = self.current_token_span; + let Some(element) = f(self) else { + if let Some(end) = &separated_by.until { + self.eat(end.clone()); + } + break; + }; + + if let Some(separator) = &separated_by.token { + if !trailing_separator && !elements.is_empty() { + self.expected_token_separating_items(separator.clone(), items, start_span); + } + } + + elements.push(element); + + trailing_separator = if let Some(separator) = &separated_by.token { + self.eat(separator.clone()) + } else { + true + }; + + if !trailing_separator && !separated_by.continue_if_separator_is_missing { + if let Some(end) = &separated_by.until { + self.eat(end.clone()); + } + break; + } + } + + (elements, trailing_separator) + } +} + +pub(super) struct SeparatedBy { + pub(super) token: Option, + pub(super) until: Option, + pub(super) continue_if_separator_is_missing: bool, +} + +impl SeparatedBy { + pub(super) fn until(self, token: Token) -> SeparatedBy { + SeparatedBy { until: Some(token), ..self } + } + + pub(super) fn stop_if_separator_is_missing(self) -> SeparatedBy { + SeparatedBy { continue_if_separator_is_missing: false, ..self } + } +} + +pub(super) fn separated_by(token: Token) -> SeparatedBy { + SeparatedBy { token: Some(token), until: None, continue_if_separator_is_missing: true } +} + +pub(super) fn separated_by_comma() -> SeparatedBy { + separated_by(Token::Comma) +} + +pub(super) fn separated_by_comma_until_right_paren() -> SeparatedBy { + separated_by_comma().until(Token::RightParen) +} + +pub(super) fn separated_by_comma_until_right_brace() -> SeparatedBy { + separated_by_comma().until(Token::RightBrace) +} + +pub(super) fn without_separator() -> SeparatedBy { + SeparatedBy { token: None, until: None, continue_if_separator_is_missing: true } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs index 4babd0f6730..99aedc6df89 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/path.rs @@ -1,172 +1,356 @@ -use crate::ast::{ - AsTraitPath, ExpressionKind, Ident, Path, PathKind, PathSegment, TypePath, UnresolvedType, -}; -use crate::parser::{NoirParser, ParserError, ParserErrorReason}; +use crate::ast::{AsTraitPath, Ident, Path, PathKind, PathSegment, UnresolvedType}; +use crate::parser::ParserErrorReason; use crate::token::{Keyword, Token}; -use chumsky::prelude::*; use noirc_errors::Span; -use super::keyword; -use super::primitives::{ident, path_segment, path_segment_no_turbofish, turbofish}; -use super::types::{generic_type_args, primitive_type}; +use crate::{parser::labels::ParsingRuleLabel, token::TokenKind}; -pub(super) fn path<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - path_inner(path_segment(type_parser)) -} +use super::Parser; -pub fn path_no_turbofish() -> impl NoirParser { - path_inner(path_segment_no_turbofish()) -} +impl<'a> Parser<'a> { + #[cfg(test)] + pub(crate) fn parse_path_or_error(&mut self) -> Path { + if let Some(path) = self.parse_path() { + path + } else { + self.expected_label(ParsingRuleLabel::Path); -fn path_inner<'a>(segment: impl NoirParser + 'a) -> impl NoirParser + 'a { - let segments = segment - .separated_by(just(Token::DoubleColon)) - .at_least(1) - .then(just(Token::DoubleColon).then_ignore(none_of(Token::LeftBrace).rewind()).or_not()) - .validate(|(path_segments, trailing_colons), span, emit_error| { - if trailing_colons.is_some() { - emit_error(ParserError::with_reason( - ParserErrorReason::ExpectedIdentifierAfterColons, - span, - )); + Path { + segments: Vec::new(), + kind: PathKind::Plain, + span: self.span_at_previous_token_end(), } - path_segments - }); - let make_path = |kind| move |segments, span| Path { segments, kind, span }; - - let prefix = |key| keyword(key).ignore_then(just(Token::DoubleColon)); - let path_kind = - |key, kind| prefix(key).ignore_then(segments.clone()).map_with_span(make_path(kind)); - - choice(( - path_kind(Keyword::Crate, PathKind::Crate), - path_kind(Keyword::Dep, PathKind::Dep), - path_kind(Keyword::Super, PathKind::Super), - segments.map_with_span(make_path(PathKind::Plain)), - )) -} + } + } -/// Parses `::path_segment` -/// These paths only support exactly two segments. -pub(super) fn as_trait_path<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - just(Token::Less) - .ignore_then(type_parser.clone()) - .then_ignore(keyword(Keyword::As)) - .then(path(type_parser.clone())) - .then(generic_type_args(type_parser)) - .then_ignore(just(Token::Greater)) - .then_ignore(just(Token::DoubleColon)) - .then(ident()) - .map(|(((typ, trait_path), trait_generics), impl_item)| AsTraitPath { - typ, - trait_path, - trait_generics, - impl_item, - }) -} + /// Tries to parse a Path. + /// Note that `crate::`, `super::`, etc., are not valid paths on their own. + /// + /// Path = PathKind identifier Turbofish? ( '::' identifier Turbofish? )* + /// + /// Turbofish = '::' PathGenerics + pub(crate) fn parse_path(&mut self) -> Option { + self.parse_path_impl( + true, // allow turbofish + true, // allow trailing double colon + ) + } -/// Parses `MyType::path_segment` -/// These paths only support exactly two segments. -/// Unlike normal paths `MyType` here can also be a primitive type or interned type -/// in addition to a named type. -pub(super) fn type_path<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - primitive_type() - .then_ignore(just(Token::DoubleColon)) - .then(ident().or_not()) - .then(turbofish(type_parser)) - .validate(|((typ, item), turbofish), span, emit| { - let turbofish = turbofish.unwrap_or_default(); - let item = if let Some(item) = item { - item - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedIdentifierAfterColons, - span, - )); - Ident::new(String::new(), Span::from(span.end()..span.end())) - }; - ExpressionKind::TypePath(TypePath { typ, item, turbofish }) - }) -} + pub(crate) fn parse_path_no_turbofish_or_error(&mut self) -> Path { + if let Some(path) = self.parse_path_no_turbofish() { + path + } else { + self.expected_label(ParsingRuleLabel::Path); + + Path { + segments: Vec::new(), + kind: PathKind::Plain, + span: self.span_at_previous_token_end(), + } + } + } -fn empty_path() -> impl NoirParser { - let make_path = |kind| move |_, span| Path { segments: Vec::new(), kind, span }; - let path_kind = |key, kind| keyword(key).map_with_span(make_path(kind)); + /// PathNoTurbofish = PathKind identifier ( '::' identifier )* + pub fn parse_path_no_turbofish(&mut self) -> Option { + self.parse_path_impl( + false, // allow turbofish + true, // allow trailing double colon + ) + } - choice((path_kind(Keyword::Crate, PathKind::Crate), path_kind(Keyword::Dep, PathKind::Plain))) -} + pub(super) fn parse_path_impl( + &mut self, + allow_turbofish: bool, + allow_trailing_double_colon: bool, + ) -> Option { + let start_span = self.current_token_span; + + let kind = self.parse_path_kind(); + + let path = self.parse_optional_path_after_kind( + kind, + allow_turbofish, + allow_trailing_double_colon, + start_span, + )?; + if path.segments.is_empty() { + if path.kind != PathKind::Plain { + self.expected_identifier(); + } + None + } else { + Some(path) + } + } + + pub(super) fn parse_optional_path_after_kind( + &mut self, + kind: PathKind, + allow_turbofish: bool, + allow_trailing_double_colon: bool, + start_span: Span, + ) -> Option { + let path = self.parse_path_after_kind( + kind, + allow_turbofish, + allow_trailing_double_colon, + start_span, + ); + + if path.segments.is_empty() && path.kind == PathKind::Plain { + None + } else { + Some(path) + } + } + + /// Parses a path assuming the path's kind (plain, `crate::`, `super::`, etc.) + /// was already parsed. Note that this method always returns a Path, even if it + /// ends up being just `crate::` or an empty path. + pub(super) fn parse_path_after_kind( + &mut self, + kind: PathKind, + allow_turbofish: bool, + allow_trailing_double_colon: bool, + start_span: Span, + ) -> Path { + let mut segments = Vec::new(); + + if self.token.kind() == TokenKind::Ident { + loop { + let ident = self.eat_ident().unwrap(); + let span = ident.span(); + + let generics = if allow_turbofish + && self.at(Token::DoubleColon) + && self.next_is(Token::Less) + { + self.bump(); + self.parse_path_generics(ParserErrorReason::AssociatedTypesNotAllowedInPaths) + } else { + None + }; + + segments.push(PathSegment { ident, generics, span }); + + if self.at(Token::DoubleColon) + && matches!(self.next_token.token(), Token::Ident(..)) + { + // Skip the double colons + self.bump(); + } else { + if allow_trailing_double_colon && self.eat_double_colon() { + self.expected_identifier(); + break; + } + + break; + } + } + } + + Path { segments, kind, span: self.span_since(start_span) } + } + + /// PathGenerics = GenericTypeArgs + pub(super) fn parse_path_generics( + &mut self, + on_named_arg_error: ParserErrorReason, + ) -> Option> { + if self.token.token() != &Token::Less { + return None; + }; -pub(super) fn maybe_empty_path() -> impl NoirParser { - path_no_turbofish().or(empty_path()) + let generics = self.parse_generic_type_args(); + for (name, _typ) in &generics.named_args { + self.push_error(on_named_arg_error.clone(), name.span()); + } + + Some(generics.ordered_args) + } + + /// PathKind + /// | 'crate' '::' + /// | 'dep' '::' + /// | 'super' '::' + /// | nothing + pub(super) fn parse_path_kind(&mut self) -> PathKind { + let kind = if self.eat_keyword(Keyword::Crate) { + PathKind::Crate + } else if self.eat_keyword(Keyword::Dep) { + PathKind::Dep + } else if self.eat_keyword(Keyword::Super) { + PathKind::Super + } else { + PathKind::Plain + }; + if kind != PathKind::Plain { + self.eat_or_error(Token::DoubleColon); + } + kind + } + + /// AsTraitPath = '<' Type 'as' PathNoTurbofish GenericTypeArgs '>' '::' identifier + pub(super) fn parse_as_trait_path(&mut self) -> Option { + if !self.eat_less() { + return None; + } + + let typ = self.parse_type_or_error(); + self.eat_keyword_or_error(Keyword::As); + let trait_path = self.parse_path_no_turbofish_or_error(); + let trait_generics = self.parse_generic_type_args(); + self.eat_or_error(Token::Greater); + self.eat_or_error(Token::DoubleColon); + let impl_item = if let Some(ident) = self.eat_ident() { + ident + } else { + self.expected_identifier(); + Ident::new(String::new(), self.span_at_previous_token_end()) + }; + + Some(AsTraitPath { typ, trait_path, trait_generics, impl_item }) + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::{ - parse_type, - parser::test_helpers::{parse_all_failing, parse_recover, parse_with}, +mod tests { + + use crate::{ + ast::{Path, PathKind}, + parser::{ + parser::tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + Parser, + }, }; + fn parse_path_no_errors(src: &str) -> Path { + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + expect_no_errors(&parser.errors); + path + } + #[test] - fn parse_path() { - let cases = vec![ - ("std", vec!["std"]), - ("std::hash", vec!["std", "hash"]), - ("std::hash::collections", vec!["std", "hash", "collections"]), - ("foo::bar", vec!["foo", "bar"]), - ("crate::std::hash", vec!["std", "hash"]), - ]; - - for (src, expected_segments) in cases { - let path: Path = parse_with(path(parse_type()), src).unwrap(); - for (segment, expected) in path.segments.into_iter().zip(expected_segments) { - assert_eq!(segment.ident.0.contents, expected); - } - } + fn parses_plain_one_segment() { + let src = "foo"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Plain); + assert_eq!(path.segments.len(), 1); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + } - parse_all_failing(path(parse_type()), vec!["std::", "::std", "std::hash::", "foo::1"]); + #[test] + fn parses_plain_two_segments() { + let src = "foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Plain); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); } #[test] - fn parse_path_kinds() { - let cases = vec![ - ("std", PathKind::Plain), - ("hash::collections", PathKind::Plain), - ("crate::std::hash", PathKind::Crate), - ("super::foo", PathKind::Super), - ]; - - for (src, expected_path_kind) in cases { - let path = parse_with(path(parse_type()), src).unwrap(); - assert_eq!(path.kind, expected_path_kind); - } + fn parses_crate_two_segments() { + let src = "crate::foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Crate); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); + } - parse_all_failing( - path(parse_type()), - vec!["crate", "crate::std::crate", "foo::bar::crate", "foo::dep"], - ); + #[test] + fn parses_super_two_segments() { + let src = "super::foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Super); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); } #[test] - fn parse_path_with_trailing_colons() { - let src = "foo::bar::"; + fn parses_dep_two_segments() { + let src = "dep::foo::bar"; + let path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Dep); + assert_eq!(path.segments.len(), 2); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + assert_eq!(path.segments[1].ident.to_string(), "bar"); + assert!(path.segments[1].generics.is_none()); + } + + #[test] + fn parses_plain_one_segment_with_trailing_colons() { + let src = "foo::"; + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + assert_eq!(path.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 1); + assert_eq!(path.kind, PathKind::Plain); + assert_eq!(path.segments.len(), 1); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + assert!(path.segments[0].generics.is_none()); + } - let (path, errors) = parse_recover(path_no_turbofish(), src); - let path = path.unwrap(); + #[test] + fn parses_with_turbofish() { + let src = "foo::::bar"; + let mut path = parse_path_no_errors(src); + assert_eq!(path.kind, PathKind::Plain); assert_eq!(path.segments.len(), 2); - assert_eq!(path.segments[0].ident.0.contents, "foo"); - assert_eq!(path.segments[1].ident.0.contents, "bar"); + assert_eq!(path.segments[0].ident.to_string(), "foo"); + + let generics = path.segments.remove(0).generics; + assert_eq!(generics.unwrap().len(), 2); + + let generics = path.segments.remove(0).generics; + assert!(generics.is_none()); + } + + #[test] + fn parses_path_stops_before_trailing_double_colon() { + let src = "foo::bar::"; + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + assert_eq!(path.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 1); + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_path_with_turbofish_stops_before_trailing_double_colon() { + let src = "foo::bar::<1>::"; + let mut parser = Parser::for_str(src); + let path = parser.parse_path_or_error(); + assert_eq!(path.span.end() as usize, src.len()); + assert_eq!(parser.errors.len(), 1); + assert_eq!(path.to_string(), "foo::bar::<1>"); + } + + #[test] + fn errors_on_crate_double_colons() { + let src = " + crate:: + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let path = parser.parse_path(); + assert!(path.is_none()); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected an identifier after ::"); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected an identifier but found end of input"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs new file mode 100644 index 00000000000..a10fe18fd79 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/pattern.rs @@ -0,0 +1,372 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, Path, Pattern}, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, +}; + +use super::{ + parse_many::{separated_by_comma_until_right_brace, separated_by_comma_until_right_paren}, + Parser, +}; + +pub(crate) enum PatternOrSelf { + Pattern(Pattern), + SelfPattern(SelfPattern), +} + +/// SelfPattern is guaranteed to be `self`, `&self` or `&mut self` without a colon following it. +pub(crate) struct SelfPattern { + pub(crate) reference: bool, + pub(crate) mutable: bool, +} + +impl<'a> Parser<'a> { + pub(crate) fn parse_pattern_or_error(&mut self) -> Pattern { + if let Some(pattern) = self.parse_pattern() { + return pattern; + } + + self.expected_label(ParsingRuleLabel::Pattern); + Pattern::Identifier(Ident::new(String::new(), self.span_at_previous_token_end())) + } + + /// Pattern + /// = 'mut' PatternNoMut + pub(crate) fn parse_pattern(&mut self) -> Option { + let start_span = self.current_token_span; + let mutable = self.eat_keyword(Keyword::Mut); + self.parse_pattern_after_modifiers(mutable, start_span) + } + + /// PatternOrSelf + /// = Pattern + /// | SelfPattern + pub(crate) fn parse_pattern_or_self(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.next_is_colon() && self.eat_self() { + return Some(PatternOrSelf::SelfPattern(SelfPattern { + reference: false, + mutable: false, + })); + } + + if self.eat_keyword(Keyword::Mut) { + if !self.next_is_colon() && self.eat_self() { + return Some(PatternOrSelf::SelfPattern(SelfPattern { + reference: false, + mutable: true, + })); + } else { + return Some(PatternOrSelf::Pattern( + self.parse_pattern_after_modifiers(true, start_span)?, + )); + } + } + + if self.at(Token::Ampersand) && self.next_is(Token::Keyword(Keyword::Mut)) { + self.bump(); + self.bump(); + if !self.next_is_colon() && self.eat_self() { + return Some(PatternOrSelf::SelfPattern(SelfPattern { + reference: true, + mutable: true, + })); + } else { + self.push_error( + ParserErrorReason::RefMutCanOnlyBeUsedWithSelf, + self.current_token_span, + ); + return Some(PatternOrSelf::Pattern( + self.parse_pattern_after_modifiers(true, start_span)?, + )); + } + } + + Some(PatternOrSelf::Pattern(self.parse_pattern_after_modifiers(false, start_span)?)) + } + + fn next_is_colon(&self) -> bool { + self.next_is(Token::Colon) + } + + pub(crate) fn parse_pattern_after_modifiers( + &mut self, + mutable: bool, + start_span: Span, + ) -> Option { + let pattern = self.parse_pattern_no_mut()?; + Some(if mutable { + Pattern::Mutable( + Box::new(pattern), + self.span_since(start_span), + false, // is synthesized + ) + } else { + pattern + }) + } + + /// PatternNoMut + /// = InternedPattern + /// | TuplePattern + /// | StructPattern + /// | IdentifierPattern + /// + /// IdentifierPattern = identifier + fn parse_pattern_no_mut(&mut self) -> Option { + let start_span = self.current_token_span; + + if let Some(pattern) = self.parse_interned_pattern() { + return Some(pattern); + } + + if let Some(pattern) = self.parse_tuple_pattern() { + return Some(pattern); + } + + let Some(mut path) = self.parse_path() else { + if self.at_built_in_type() { + self.push_error( + ParserErrorReason::ExpectedPatternButFoundType(self.token.token().clone()), + self.current_token_span, + ); + } + return None; + }; + + if self.eat_left_brace() { + return Some(self.parse_struct_pattern(path, start_span)); + } + + if !path.is_ident() { + self.push_error(ParserErrorReason::InvalidPattern, path.span); + + let ident = path.segments.pop().unwrap().ident; + return Some(Pattern::Identifier(ident)); + } + + let ident = path.segments.remove(0).ident; + Some(Pattern::Identifier(ident)) + } + + /// InternedPattern = interned_pattern + fn parse_interned_pattern(&mut self) -> Option { + let Some(token) = self.eat_kind(TokenKind::InternedPattern) else { + return None; + }; + + match token.into_token() { + Token::InternedPattern(pattern) => { + Some(Pattern::Interned(pattern, self.previous_token_span)) + } + _ => unreachable!(), + } + } + + /// TuplePattern = '(' PatternList? ')' + /// + /// PatternList = Pattern ( ',' Pattern )* ','? + fn parse_tuple_pattern(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat_left_paren() { + return None; + } + + let patterns = self.parse_many( + "tuple elements", + separated_by_comma_until_right_paren(), + Self::parse_tuple_pattern_element, + ); + + Some(Pattern::Tuple(patterns, self.span_since(start_span))) + } + + fn parse_tuple_pattern_element(&mut self) -> Option { + if let Some(pattern) = self.parse_pattern() { + Some(pattern) + } else { + self.expected_label(ParsingRuleLabel::Pattern); + None + } + } + + /// StructPattern = Path '{' StructPatternFields? '}' + /// + /// StructPatternFields = StructPatternField ( ',' StructPatternField )? ','? + /// + /// StructPatternField = identifier ( ':' Pattern )? + fn parse_struct_pattern(&mut self, path: Path, start_span: Span) -> Pattern { + let fields = self.parse_many( + "struct fields", + separated_by_comma_until_right_brace(), + Self::parse_struct_pattern_field, + ); + + Pattern::Struct(path, fields, self.span_since(start_span)) + } + + fn parse_struct_pattern_field(&mut self) -> Option<(Ident, Pattern)> { + let Some(ident) = self.eat_ident() else { + self.expected_identifier(); + return None; + }; + + Some(if self.eat_colon() { + (ident, self.parse_pattern_or_error()) + } else { + (ident.clone(), Pattern::Identifier(ident)) + }) + } + + fn at_built_in_type(&self) -> bool { + matches!( + self.token.token(), + Token::Bool(..) + | Token::IntType(..) + | Token::Keyword(Keyword::Bool) + | Token::Keyword(Keyword::CtString) + | Token::Keyword(Keyword::Expr) + | Token::Keyword(Keyword::Field) + | Token::Keyword(Keyword::FunctionDefinition) + | Token::Keyword(Keyword::Module) + | Token::Keyword(Keyword::Quoted) + | Token::Keyword(Keyword::StructDefinition) + | Token::Keyword(Keyword::TraitConstraint) + | Token::Keyword(Keyword::TraitDefinition) + | Token::Keyword(Keyword::TraitImpl) + | Token::Keyword(Keyword::TypedExpr) + | Token::Keyword(Keyword::TypeType) + | Token::Keyword(Keyword::UnresolvedType) + ) + } +} + +#[cfg(test)] +mod tests { + + use crate::{ + ast::Pattern, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::{Keyword, Token}, + }; + + fn parse_pattern_no_errors(src: &str) -> Pattern { + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + expect_no_errors(&parser.errors); + pattern + } + + #[test] + fn parses_identifier_pattern() { + let src = "foo"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "foo"); + } + + #[test] + fn parses_mutable_pattern() { + let src = "mut foo"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Mutable(pattern, _, _) = pattern else { panic!("Expected a mutable pattern") }; + let pattern: &Pattern = &pattern; + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "foo"); + } + + #[test] + fn parses_tuple_pattern() { + let src = "(foo, bar)"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Tuple(mut patterns, _) = pattern else { panic!("Expected a tuple pattern") }; + assert_eq!(patterns.len(), 2); + + let pattern = patterns.remove(0); + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "foo"); + + let pattern = patterns.remove(0); + let Pattern::Identifier(ident) = pattern else { panic!("Expected an identifier pattern") }; + assert_eq!(ident.to_string(), "bar"); + } + + #[test] + fn parses_unclosed_tuple_pattern() { + let src = "(foo,"; + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + assert_eq!(parser.errors.len(), 1); + let Pattern::Tuple(patterns, _) = pattern else { panic!("Expected a tuple pattern") }; + assert_eq!(patterns.len(), 1); + } + + #[test] + fn parses_struct_pattern_no_fields() { + let src = "foo::Bar {}"; + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + expect_no_errors(&parser.errors); + let Pattern::Struct(path, patterns, _) = pattern else { + panic!("Expected a struct pattern") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(patterns.is_empty()); + } + + #[test] + fn parses_struct_pattern() { + let src = "foo::Bar { x: one, y }"; + let pattern = parse_pattern_no_errors(src); + let Pattern::Struct(path, mut patterns, _) = pattern else { + panic!("Expected a struct pattern") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert_eq!(patterns.len(), 2); + + let (ident, pattern) = patterns.remove(0); + assert_eq!(ident.to_string(), "x"); + assert_eq!(pattern.to_string(), "one"); + + let (ident, pattern) = patterns.remove(0); + assert_eq!(ident.to_string(), "y"); + assert_eq!(pattern.to_string(), "y"); + } + + #[test] + fn parses_unclosed_struct_pattern() { + let src = "foo::Bar { x"; + let mut parser = Parser::for_str(src); + let pattern = parser.parse_pattern_or_error(); + assert_eq!(parser.errors.len(), 1); + let Pattern::Struct(path, _, _) = pattern else { panic!("Expected a struct pattern") }; + assert_eq!(path.to_string(), "foo::Bar"); + } + + #[test] + fn errors_on_reserved_type() { + let src = " + Field + ^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let pattern = parser.parse_pattern(); + assert!(pattern.is_none()); + + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!( + reason, + ParserErrorReason::ExpectedPatternButFoundType(Token::Keyword(Keyword::Field)) + )); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs deleted file mode 100644 index 5a040f23619..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/primitives.rs +++ /dev/null @@ -1,167 +0,0 @@ -use chumsky::prelude::*; - -use crate::ast::{ - ExpressionKind, GenericTypeArgs, Ident, PathSegment, StatementKind, UnaryOp, UnresolvedType, -}; -use crate::parser::ParserErrorReason; -use crate::{ - parser::{labels::ParsingRuleLabel, ExprParser, NoirParser, ParserError}, - token::{Keyword, Token, TokenKind}, -}; - -use super::path::{path, path_no_turbofish}; -use super::types::required_generic_type_args; - -/// This parser always parses no input and fails -pub(super) fn nothing() -> impl NoirParser { - one_of([]).map(|_| unreachable!("parser should always error")) -} - -pub(super) fn keyword(keyword: Keyword) -> impl NoirParser { - just(Token::Keyword(keyword)) -} - -pub(super) fn token_kind(token_kind: TokenKind) -> impl NoirParser { - filter_map(move |span, found: Token| { - if found.kind() == token_kind { - Ok(found) - } else { - Err(ParserError::expected_label( - ParsingRuleLabel::TokenKind(token_kind.clone()), - found, - span, - )) - } - }) -} - -pub(super) fn path_segment<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - ident().then(turbofish(type_parser)).validate(|(ident, generics), span, emit| { - if generics.as_ref().map_or(false, |generics| !generics.named_args.is_empty()) { - let reason = ParserErrorReason::AssociatedTypesNotAllowedInPaths; - emit(ParserError::with_reason(reason, span)); - } - - let generics = generics.map(|generics| generics.ordered_args); - PathSegment { ident, generics, span } - }) -} - -pub(super) fn path_segment_no_turbofish() -> impl NoirParser { - ident().map(PathSegment::from) -} - -pub(super) fn ident() -> impl NoirParser { - token_kind(TokenKind::Ident).map_with_span(Ident::from_token) -} - -// Right-shift (>>) is issued as two separate > tokens by the lexer as this makes it easier -// to parse nested generic types. For normal expressions however, it means we have to manually -// parse two greater-than tokens as a single right-shift here. -pub(super) fn right_shift_operator() -> impl NoirParser { - just(Token::Greater).then(just(Token::Greater)).to(Token::ShiftRight) -} - -pub(super) fn not

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Bang).ignore_then(term_parser).map(|rhs| ExpressionKind::prefix(UnaryOp::Not, rhs)) -} - -pub(super) fn negation

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Minus) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::Minus, rhs)) -} - -pub(super) fn mutable_reference

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Ampersand) - .ignore_then(keyword(Keyword::Mut)) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::MutableReference, rhs)) -} - -pub(super) fn dereference

(term_parser: P) -> impl NoirParser -where - P: ExprParser, -{ - just(Token::Star) - .ignore_then(term_parser) - .map(|rhs| ExpressionKind::prefix(UnaryOp::Dereference { implicitly_added: false }, rhs)) -} - -pub(super) fn turbofish<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser> + 'a { - just(Token::DoubleColon).ignore_then(required_generic_type_args(type_parser)).or_not() -} - -pub(super) fn variable() -> impl NoirParser { - path(super::parse_type()).map(ExpressionKind::Variable) -} - -pub(super) fn variable_no_turbofish() -> impl NoirParser { - path_no_turbofish().map(ExpressionKind::Variable) -} - -pub(super) fn macro_quote_marker() -> impl NoirParser { - token_kind(TokenKind::UnquoteMarker).map(|token| match token { - Token::UnquoteMarker(expr_id) => ExpressionKind::Resolved(expr_id), - other => unreachable!("Non-unquote-marker parsed as an unquote marker: {other:?}"), - }) -} - -pub(super) fn interned_expr() -> impl NoirParser { - token_kind(TokenKind::InternedExpr).map(|token| match token { - Token::InternedExpr(id) => ExpressionKind::Interned(id), - _ => unreachable!("token_kind(InternedExpr) guarantees we parse an interned expr"), - }) -} - -pub(super) fn interned_statement() -> impl NoirParser { - token_kind(TokenKind::InternedStatement).map(|token| match token { - Token::InternedStatement(id) => StatementKind::Interned(id), - _ => { - unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") - } - }) -} - -// This rule is so that we can re-parse StatementKind::Expression and Semi in -// an expression position (ignoring the semicolon) if needed. -pub(super) fn interned_statement_expr() -> impl NoirParser { - token_kind(TokenKind::InternedStatement).map(|token| match token { - Token::InternedStatement(id) => ExpressionKind::InternedStatement(id), - _ => { - unreachable!("token_kind(InternedStatement) guarantees we parse an interned statement") - } - }) -} - -#[cfg(test)] -mod test { - use crate::parser::parser::{ - expression, expression_no_constructors, fresh_statement, term, test_helpers::*, - }; - - #[test] - fn parse_unary() { - parse_all( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["!hello", "-hello", "--hello", "-!hello", "!-hello"], - ); - parse_all_failing( - term(expression(), expression_no_constructors(expression()), fresh_statement(), true), - vec!["+hello", "/hello"], - ); - } -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs new file mode 100644 index 00000000000..d118be5d54a --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/statement.rs @@ -0,0 +1,725 @@ +use noirc_errors::{Span, Spanned}; + +use crate::{ + ast::{ + AssignStatement, BinaryOp, BinaryOpKind, ConstrainKind, ConstrainStatement, Expression, + ExpressionKind, ForBounds, ForLoopStatement, ForRange, Ident, InfixExpression, LValue, + LetStatement, Statement, StatementKind, + }, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Attribute, Keyword, Token, TokenKind}, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + pub(crate) fn parse_statement_or_error(&mut self) -> Statement { + if let Some((statement, (_token, _span))) = self.parse_statement() { + statement + } else { + self.expected_label(ParsingRuleLabel::Statement); + Statement { kind: StatementKind::Error, span: self.span_at_previous_token_end() } + } + } + + /// Statement = Attributes StatementKind ';'? + pub(crate) fn parse_statement(&mut self) -> Option<(Statement, (Option, Span))> { + loop { + let attributes = self.parse_attributes(); + let start_span = self.current_token_span; + let kind = self.parse_statement_kind(attributes); + + let (semicolon_token, semicolon_span) = if self.at(Token::Semicolon) { + let token = self.token.clone(); + self.bump(); + let span = token.to_span(); + + (Some(token.into_token()), span) + } else { + (None, self.previous_token_span) + }; + + let span = self.span_since(start_span); + + if let Some(kind) = kind { + let statement = Statement { kind, span }; + return Some((statement, (semicolon_token, semicolon_span))); + } + + self.expected_label(ParsingRuleLabel::Statement); + + if semicolon_token.is_some() || self.at(Token::RightBrace) || self.at_eof() { + return None; + } else { + self.bump(); + } + } + } + + /// StatementKind + /// = BreakStatement + /// | ContinueStatement + /// | ReturnStatement + /// | LetStatement + /// | ConstrainStatement + /// | ComptimeStatement + /// | ForStatement + /// | IfStatement + /// | BlockStatement + /// | AssignStatement + /// | ExpressionStatement + /// + /// BreakStatement = 'break' + /// + /// ContinueStatement = 'continue' + /// + /// ReturnStatement = 'return' Expression? + /// + /// IfStatement = IfExpression + /// + /// BlockStatement = Block + /// + /// AssignStatement = Expression '=' Expression + /// + /// ExpressionStatement = Expression + fn parse_statement_kind( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Option { + let start_span = self.current_token_span; + + if let Some(token) = self.eat_kind(TokenKind::InternedStatement) { + match token.into_token() { + Token::InternedStatement(statement) => { + return Some(StatementKind::Interned(statement)) + } + _ => unreachable!(), + } + } + + if self.eat_keyword(Keyword::Break) { + return Some(StatementKind::Break); + } + + if self.eat_keyword(Keyword::Continue) { + return Some(StatementKind::Continue); + } + + if self.eat_keyword(Keyword::Return) { + self.parse_expression(); + self.push_error(ParserErrorReason::EarlyReturn, self.span_since(start_span)); + return Some(StatementKind::Error); + } + + if self.at_keyword(Keyword::Let) { + let let_statement = self.parse_let_statement(attributes)?; + return Some(StatementKind::Let(let_statement)); + } + + if let Some(constrain) = self.parse_constrain_statement() { + return Some(StatementKind::Constrain(constrain)); + } + + if self.at_keyword(Keyword::Comptime) { + return self.parse_comptime_statement(attributes); + } + + if let Some(for_loop) = self.parse_for() { + return Some(StatementKind::For(for_loop)); + } + + if let Some(kind) = self.parse_if_expr() { + return Some(StatementKind::Expression(Expression { + kind, + span: self.span_since(start_span), + })); + } + + if let Some(block) = self.parse_block() { + return Some(StatementKind::Expression(Expression { + kind: ExpressionKind::Block(block), + span: self.span_since(start_span), + })); + } + + if let Some(token) = self.eat_kind(TokenKind::InternedLValue) { + match token.into_token() { + Token::InternedLValue(lvalue) => { + let lvalue = LValue::Interned(lvalue, self.span_since(start_span)); + self.eat_or_error(Token::Assign); + let expression = self.parse_expression_or_error(); + return Some(StatementKind::Assign(AssignStatement { lvalue, expression })); + } + _ => unreachable!(), + } + } + + let expression = self.parse_expression()?; + + if self.eat_assign() { + if let Some(lvalue) = LValue::from_expression(expression.clone()) { + let expression = self.parse_expression_or_error(); + return Some(StatementKind::Assign(AssignStatement { lvalue, expression })); + } else { + self.push_error( + ParserErrorReason::InvalidLeftHandSideOfAssignment, + expression.span, + ); + } + } + + if let Some(operator) = self.next_is_op_assign() { + if let Some(lvalue) = LValue::from_expression(expression.clone()) { + // Desugar `a = b` to `a = a b`. This relies on the evaluation of `a` having no side effects, + // which is currently enforced by the restricted syntax of LValues. + let infix = InfixExpression { + lhs: expression, + operator, + rhs: self.parse_expression_or_error(), + }; + let expression = Expression::new( + ExpressionKind::Infix(Box::new(infix)), + self.span_since(start_span), + ); + return Some(StatementKind::Assign(AssignStatement { lvalue, expression })); + } else { + self.push_error( + ParserErrorReason::InvalidLeftHandSideOfAssignment, + expression.span, + ); + } + } + + Some(StatementKind::Expression(expression)) + } + + fn next_is_op_assign(&mut self) -> Option { + let start_span = self.current_token_span; + let operator = if self.next_is(Token::Assign) { + match self.token.token() { + Token::Plus => Some(BinaryOpKind::Add), + Token::Minus => Some(BinaryOpKind::Subtract), + Token::Star => Some(BinaryOpKind::Multiply), + Token::Slash => Some(BinaryOpKind::Divide), + Token::Percent => Some(BinaryOpKind::Modulo), + Token::Ampersand => Some(BinaryOpKind::And), + Token::Caret => Some(BinaryOpKind::Xor), + Token::ShiftLeft => Some(BinaryOpKind::ShiftLeft), + Token::Pipe => Some(BinaryOpKind::Or), + _ => None, + } + } else if self.at(Token::Greater) && self.next_is(Token::GreaterEqual) { + // >>= + Some(BinaryOpKind::ShiftRight) + } else { + None + }; + + if let Some(operator) = operator { + self.bump(); + self.bump(); + Some(Spanned::from(self.span_since(start_span), operator)) + } else { + None + } + } + + /// ForStatement = 'for' identifier 'in' ForRange Block + fn parse_for(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat_keyword(Keyword::For) { + return None; + } + + let Some(identifier) = self.eat_ident() else { + self.expected_identifier(); + let identifier = Ident::default(); + return Some(self.empty_for_loop(identifier, start_span)); + }; + + if !self.eat_keyword(Keyword::In) { + self.expected_token(Token::Keyword(Keyword::In)); + return Some(self.empty_for_loop(identifier, start_span)); + } + + let range = self.parse_for_range(); + + let block_start_span = self.current_token_span; + let block = if let Some(block) = self.parse_block() { + Expression { + kind: ExpressionKind::Block(block), + span: self.span_since(block_start_span), + } + } else { + self.expected_token(Token::LeftBrace); + Expression { kind: ExpressionKind::Error, span: self.span_since(block_start_span) } + }; + + Some(ForLoopStatement { identifier, range, block, span: self.span_since(start_span) }) + } + + /// ForRange + /// = ExpressionExceptConstructor + /// | ExpressionExceptConstructor '..' ExpressionExceptConstructor + fn parse_for_range(&mut self) -> ForRange { + let expr = self.parse_expression_except_constructor_or_error(); + + if self.eat(Token::DoubleDot) { + let end = self.parse_expression_except_constructor_or_error(); + ForRange::Range(ForBounds { start: expr, end, inclusive: false }) + } else if self.eat(Token::DoubleDotEqual) { + let end = self.parse_expression_except_constructor_or_error(); + ForRange::Range(ForBounds { start: expr, end, inclusive: true }) + } else { + ForRange::Array(expr) + } + } + + fn empty_for_loop(&mut self, identifier: Ident, start_span: Span) -> ForLoopStatement { + ForLoopStatement { + identifier, + range: ForRange::Array(Expression { + kind: ExpressionKind::Error, + span: Span::default(), + }), + block: Expression { kind: ExpressionKind::Error, span: Span::default() }, + span: self.span_since(start_span), + } + } + + /// ComptimeStatement + /// = ComptimeBlock + /// | ComptimeLet + /// | ComptimeFor + /// + /// ComptimeBlock = 'comptime' Block + /// + /// ComptimeLet = 'comptime' LetStatement + /// + /// ComptimeFor = 'comptime' ForStatement + fn parse_comptime_statement( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Option { + let start_span = self.current_token_span; + + if !self.eat_keyword(Keyword::Comptime) { + return None; + } + + if let Some(kind) = self.parse_comptime_statement_kind(attributes) { + return Some(StatementKind::Comptime(Box::new(Statement { + kind, + span: self.span_since(start_span), + }))); + } + + self.expected_one_of_tokens(&[ + Token::LeftBrace, + Token::Keyword(Keyword::Let), + Token::Keyword(Keyword::For), + ]); + + None + } + + fn parse_comptime_statement_kind( + &mut self, + attributes: Vec<(Attribute, Span)>, + ) -> Option { + let start_span = self.current_token_span; + + if let Some(block) = self.parse_block() { + return Some(StatementKind::Expression(Expression { + kind: ExpressionKind::Block(block), + span: self.span_since(start_span), + })); + } + + if let Some(let_statement) = self.parse_let_statement(attributes) { + return Some(StatementKind::Let(let_statement)); + } + + if let Some(for_loop) = self.parse_for() { + return Some(StatementKind::For(for_loop)); + } + + None + } + + /// LetStatement = 'let' pattern OptionalTypeAnnotation '=' Expression + fn parse_let_statement(&mut self, attributes: Vec<(Attribute, Span)>) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let attributes = self.validate_secondary_attributes(attributes); + let pattern = self.parse_pattern_or_error(); + let r#type = self.parse_optional_type_annotation(); + let expression = if self.eat_assign() { + self.parse_expression_or_error() + } else { + self.expected_token(Token::Assign); + Expression { kind: ExpressionKind::Error, span: self.current_token_span } + }; + + Some(LetStatement { pattern, r#type, expression, attributes, comptime: false }) + } + + /// ConstrainStatement + /// = 'constrain' Expression + /// | 'assert' Arguments + /// | 'assert_eq' Arguments + fn parse_constrain_statement(&mut self) -> Option { + let start_span = self.current_token_span; + let Some(kind) = self.parse_constrain_kind() else { + return None; + }; + + Some(match kind { + ConstrainKind::Assert | ConstrainKind::AssertEq => { + let arguments = self.parse_arguments(); + if arguments.is_none() { + self.expected_token(Token::LeftParen); + } + let arguments = arguments.unwrap_or_default(); + + ConstrainStatement { kind, arguments, span: self.span_since(start_span) } + } + ConstrainKind::Constrain => { + self.push_error(ParserErrorReason::ConstrainDeprecated, self.previous_token_span); + + let expression = self.parse_expression_or_error(); + ConstrainStatement { + kind, + arguments: vec![expression], + span: self.span_since(start_span), + } + } + }) + } + + fn parse_constrain_kind(&mut self) -> Option { + if self.eat_keyword(Keyword::Assert) { + Some(ConstrainKind::Assert) + } else if self.eat_keyword(Keyword::AssertEq) { + Some(ConstrainKind::AssertEq) + } else if self.eat_keyword(Keyword::Constrain) { + Some(ConstrainKind::Constrain) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ + ConstrainKind, ExpressionKind, ForRange, LValue, Statement, StatementKind, + UnresolvedTypeData, + }, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + }; + + fn parse_statement_no_errors(src: &str) -> Statement { + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement_or_error(); + expect_no_errors(&parser.errors); + statement + } + + #[test] + fn parses_break() { + let src = "break"; + let statement = parse_statement_no_errors(src); + assert!(matches!(statement.kind, StatementKind::Break)); + } + + #[test] + fn parses_continue() { + let src = "continue"; + let statement = parse_statement_no_errors(src); + assert!(matches!(statement.kind, StatementKind::Continue)); + } + + #[test] + fn parses_let_statement_no_type() { + let src = "let x = 1;"; + let statement = parse_statement_no_errors(src); + let StatementKind::Let(let_statement) = statement.kind else { + panic!("Expected let statement"); + }; + assert_eq!(let_statement.pattern.to_string(), "x"); + assert!(matches!(let_statement.r#type.typ, UnresolvedTypeData::Unspecified)); + assert_eq!(let_statement.expression.to_string(), "1"); + assert!(!let_statement.comptime); + } + + #[test] + fn parses_let_statement_with_type() { + let src = "let x: Field = 1;"; + let statement = parse_statement_no_errors(src); + let StatementKind::Let(let_statement) = statement.kind else { + panic!("Expected let statement"); + }; + assert_eq!(let_statement.pattern.to_string(), "x"); + assert_eq!(let_statement.r#type.to_string(), "Field"); + assert_eq!(let_statement.expression.to_string(), "1"); + assert!(!let_statement.comptime); + } + + #[test] + fn parses_assert() { + let src = "assert(true, \"good\")"; + let statement = parse_statement_no_errors(src); + let StatementKind::Constrain(constrain) = statement.kind else { + panic!("Expected constrain statement"); + }; + assert_eq!(constrain.kind, ConstrainKind::Assert); + assert_eq!(constrain.arguments.len(), 2); + } + + #[test] + fn parses_assert_eq() { + let src = "assert_eq(1, 2, \"bad\")"; + let statement = parse_statement_no_errors(src); + let StatementKind::Constrain(constrain) = statement.kind else { + panic!("Expected constrain statement"); + }; + assert_eq!(constrain.kind, ConstrainKind::AssertEq); + assert_eq!(constrain.arguments.len(), 3); + } + + #[test] + fn parses_constrain() { + let src = " + constrain 1 + ^^^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let statement = parser.parse_statement_or_error(); + let StatementKind::Constrain(constrain) = statement.kind else { + panic!("Expected constrain statement"); + }; + assert_eq!(constrain.kind, ConstrainKind::Constrain); + assert_eq!(constrain.arguments.len(), 1); + + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::ConstrainDeprecated)); + } + + #[test] + fn parses_comptime_block() { + let src = "comptime { 1 }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Comptime(statement) = statement.kind else { + panic!("Expected comptime statement"); + }; + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expression statement"); + }; + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block expression"); + }; + assert_eq!(block.statements.len(), 1); + } + + #[test] + fn parses_comptime_let() { + let src = "comptime let x = 1;"; + let statement = parse_statement_no_errors(src); + let StatementKind::Comptime(statement) = statement.kind else { + panic!("Expected comptime statement"); + }; + let StatementKind::Let(..) = statement.kind else { + panic!("Expected let statement"); + }; + } + + #[test] + fn parses_for_array() { + let src = "for i in x { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + let ForRange::Array(expr) = for_loop.range else { + panic!("Expected array"); + }; + assert_eq!(expr.to_string(), "x"); + } + + #[test] + fn parses_for_range() { + let src = "for i in 0..10 { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + let ForRange::Range(bounds) = for_loop.range else { + panic!("Expected range"); + }; + assert_eq!(bounds.start.to_string(), "0"); + assert_eq!(bounds.end.to_string(), "10"); + assert!(!bounds.inclusive); + } + + #[test] + fn parses_for_range_inclusive() { + let src = "for i in 0..=10 { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + let ForRange::Range(bounds) = for_loop.range else { + panic!("Expected range"); + }; + assert_eq!(bounds.start.to_string(), "0"); + assert_eq!(bounds.end.to_string(), "10"); + assert!(bounds.inclusive); + } + + #[test] + fn parses_comptime_for() { + let src = "comptime for i in x { }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Comptime(statement) = statement.kind else { + panic!("Expected comptime"); + }; + let StatementKind::For(for_loop) = statement.kind else { + panic!("Expected for loop"); + }; + assert_eq!(for_loop.identifier.to_string(), "i"); + assert!(matches!(for_loop.range, ForRange::Array(..))); + } + + #[test] + fn parses_assignment() { + let src = "x = 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(assign) = statement.kind else { + panic!("Expected assign"); + }; + let LValue::Ident(ident) = assign.lvalue else { + panic!("Expected ident"); + }; + assert_eq!(ident.to_string(), "x"); + assert_eq!(assign.expression.to_string(), "1"); + } + + #[test] + fn parses_assignment_with_parentheses() { + let src = "(x)[0] = 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(..) = statement.kind else { + panic!("Expected assign"); + }; + } + + #[test] + fn parses_op_assignment() { + let src = "x += 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(assign) = statement.kind else { + panic!("Expected assign"); + }; + assert_eq!(assign.to_string(), "x = (x + 1)"); + } + + #[test] + fn parses_op_assignment_with_shift_right() { + let src = "x >>= 1"; + let statement = parse_statement_no_errors(src); + let StatementKind::Assign(assign) = statement.kind else { + panic!("Expected assign"); + }; + assert_eq!(assign.to_string(), "x = (x >> 1)"); + } + + #[test] + fn parses_if_statement_followed_by_tuple() { + // This shouldn't be parsed as a call + let src = "{ if 1 { 2 } (3, 4) }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expr"); + }; + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block"); + }; + assert_eq!(block.statements.len(), 2); + } + + #[test] + fn parses_block_followed_by_tuple() { + // This shouldn't be parsed as a call + let src = "{ { 2 } (3, 4) }"; + let statement = parse_statement_no_errors(src); + let StatementKind::Expression(expr) = statement.kind else { + panic!("Expected expr"); + }; + let ExpressionKind::Block(block) = expr.kind else { + panic!("Expected block"); + }; + assert_eq!(block.statements.len(), 2); + } + + #[test] + fn errors_on_return_statement() { + // This shouldn't be parsed as a call + let src = " + return 1 + ^^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let statement = parser.parse_statement_or_error(); + assert!(matches!(statement.kind, StatementKind::Error)); + let reason = get_single_error_reason(&parser.errors, span); + assert!(matches!(reason, ParserErrorReason::EarlyReturn)); + } + + #[test] + fn recovers_on_unknown_statement_followed_by_actual_statement() { + let src = " + ] let x = 1; + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let statement = parser.parse_statement_or_error(); + assert!(matches!(statement.kind, StatementKind::Let(..))); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a statement but found ]"); + } + + #[test] + fn recovers_on_unknown_statement_followed_by_semicolon() { + let src = " ] ;"; + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement(); + assert!(statement.is_none()); + assert_eq!(parser.errors.len(), 2); + } + + #[test] + fn recovers_on_unknown_statement_followed_by_right_brace() { + let src = " ] }"; + let mut parser = Parser::for_str(src); + let statement = parser.parse_statement(); + assert!(statement.is_none()); + assert_eq!(parser.errors.len(), 2); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs index 729f276e9b8..6775cf35a78 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/structs.rs @@ -1,84 +1,270 @@ -use chumsky::prelude::*; +use noirc_errors::Span; -use crate::ast::{Documented, NoirStruct, StructField}; -use crate::parser::parser::visibility::item_visibility; use crate::{ - parser::{ - parser::{ - attributes::{attributes, validate_secondary_attributes}, - function, parse_type, - primitives::{ident, keyword}, - }, - NoirParser, TopLevelStatementKind, - }, - token::{Keyword, Token}, + ast::{Documented, Ident, ItemVisibility, NoirStruct, StructField, UnresolvedGenerics}, + parser::ParserErrorReason, + token::{Attribute, SecondaryAttribute, Token}, }; -use super::doc_comments::outer_doc_comments; - -pub(super) fn struct_definition() -> impl NoirParser { - use self::Keyword::Struct; - use Token::*; - - let fields = struct_fields() - .delimited_by(just(LeftBrace), just(RightBrace)) - .recover_with(nested_delimiters( - LeftBrace, - RightBrace, - [(LeftParen, RightParen), (LeftBracket, RightBracket)], - |_| vec![], - )) - .or(just(Semicolon).to(Vec::new())); - - attributes() - .then(item_visibility()) - .then_ignore(keyword(Struct)) - .then(ident()) - .then(function::generics()) - .then(fields) - .validate(|((((attributes, visibility), name), generics), fields), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Struct(NoirStruct { - name, +use super::{parse_many::separated_by_comma_until_right_brace, Parser}; + +impl<'a> Parser<'a> { + /// Struct = 'struct' identifier Generics '{' StructField* '}' + /// + /// StructField = OuterDocComments identifier ':' Type + pub(crate) fn parse_struct( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirStruct { + let attributes = self.validate_secondary_attributes(attributes); + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return self.empty_struct( + Ident::default(), attributes, visibility, - generics, - fields, - span, - }) - }) -} + Vec::new(), + start_span, + ); + }; + + let generics = self.parse_generics(); + + if self.eat_semicolons() { + return self.empty_struct(name, attributes, visibility, generics, start_span); + } + + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return self.empty_struct(name, attributes, visibility, generics, start_span); + } + + let fields = self.parse_many( + "struct fields", + separated_by_comma_until_right_brace(), + Self::parse_struct_field, + ); + + NoirStruct { + name, + attributes, + visibility, + generics, + fields, + span: self.span_since(start_span), + } + } + + fn parse_struct_field(&mut self) -> Option> { + let mut doc_comments; + let name; + + // Loop until we find an identifier, skipping anything that's not one + loop { + let doc_comments_start_span = self.current_token_span; + doc_comments = self.parse_outer_doc_comments(); + + if let Some(ident) = self.eat_ident() { + name = ident; + break; + } -fn struct_fields() -> impl NoirParser>> { - let field = ident().then_ignore(just(Token::Colon)).then(parse_type()); - let field = outer_doc_comments().then(field).map(|(doc_comments, (name, typ))| { - Documented::new(StructField { name, typ }, doc_comments) - }); - field.separated_by(just(Token::Comma)).allow_trailing() + if !doc_comments.is_empty() { + self.push_error( + ParserErrorReason::DocCommentDoesNotDocumentAnything, + self.span_since(doc_comments_start_span), + ); + } + + // Though we do have to stop at EOF + if self.at_eof() { + self.expected_token(Token::RightBrace); + return None; + } + + // Or if we find a right brace + if self.at(Token::RightBrace) { + return None; + } + + self.expected_identifier(); + self.bump(); + } + + self.eat_or_error(Token::Colon); + + let typ = self.parse_type_or_error(); + Some(Documented::new(StructField { name, typ }, doc_comments)) + } + + fn empty_struct( + &self, + name: Ident, + attributes: Vec, + visibility: ItemVisibility, + generics: UnresolvedGenerics, + start_span: Span, + ) -> NoirStruct { + NoirStruct { + name, + attributes, + visibility, + generics, + fields: Vec::new(), + span: self.span_since(start_span), + } + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; +mod tests { + use crate::{ + ast::{IntegerBitSize, NoirStruct, Signedness, UnresolvedGeneric, UnresolvedTypeData}, + parser::{ + parser::{ + parse_program, + tests::{ + expect_no_errors, get_single_error, get_single_error_reason, + get_source_with_error_span, + }, + }, + ItemKind, ParserErrorReason, + }, + }; + + fn parse_struct_no_errors(src: &str) -> NoirStruct { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Struct(noir_struct) = item.kind else { + panic!("Expected struct"); + }; + noir_struct + } #[test] - fn parse_structs() { - let cases = vec![ - "struct Foo;", - "struct Foo { }", - "struct Bar { ident: Field, }", - "struct Baz { ident: Field, other: Field }", - "#[attribute] struct Baz { ident: Field, other: Field }", - ]; - parse_all(struct_definition(), cases); - - let failing = vec![ - "struct { }", - "struct Foo { bar: pub Field }", - "struct Foo { bar: pub Field }", - "#[oracle(some)] struct Foo { bar: Field }", - ]; - parse_all_failing(struct_definition(), failing); + fn parse_empty_struct() { + let src = "struct Foo {}"; + let noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert!(noir_struct.fields.is_empty()); + assert!(noir_struct.generics.is_empty()); + } + + #[test] + fn parse_empty_struct_followed_by_semicolon() { + let src = "struct Foo;"; + let noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert!(noir_struct.fields.is_empty()); + assert!(noir_struct.generics.is_empty()); + } + + #[test] + fn parse_empty_struct_with_generics() { + let src = "struct Foo {}"; + let mut noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert!(noir_struct.fields.is_empty()); + assert_eq!(noir_struct.generics.len(), 2); + + let generic = noir_struct.generics.remove(0); + let UnresolvedGeneric::Variable(ident) = generic else { + panic!("Expected generic variable"); + }; + assert_eq!("A", ident.to_string()); + + let generic = noir_struct.generics.remove(0); + let UnresolvedGeneric::Numeric { ident, typ } = generic else { + panic!("Expected generic numeric"); + }; + assert_eq!("B", ident.to_string()); + assert_eq!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + ); + } + + #[test] + fn parse_struct_with_fields() { + let src = "struct Foo { x: i32, y: Field }"; + let mut noir_struct = parse_struct_no_errors(src); + assert_eq!("Foo", noir_struct.name.to_string()); + assert_eq!(noir_struct.fields.len(), 2); + + let field = noir_struct.fields.remove(0).item; + assert_eq!("x", field.name.to_string()); + assert!(matches!( + field.typ.typ, + UnresolvedTypeData::Integer(Signedness::Signed, IntegerBitSize::ThirtyTwo) + )); + + let field = noir_struct.fields.remove(0).item; + assert_eq!("y", field.name.to_string()); + assert!(matches!(field.typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parse_empty_struct_with_doc_comments() { + let src = "/// Hello\nstruct Foo {}"; + let (module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + assert_eq!(item.doc_comments.len(), 1); + let ItemKind::Struct(noir_struct) = &item.kind else { + panic!("Expected struct"); + }; + assert_eq!("Foo", noir_struct.name.to_string()); + } + + #[test] + fn parse_unclosed_struct() { + let src = "struct Foo {"; + let (module, errors) = parse_program(src); + assert_eq!(errors.len(), 1); + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Struct(noir_struct) = &item.kind else { + panic!("Expected struct"); + }; + assert_eq!("Foo", noir_struct.name.to_string()); + } + + #[test] + fn parse_error_no_function_attributes_allowed_on_struct() { + let src = " + #[test] struct Foo {} + ^^^^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let (_, errors) = parse_program(&src); + let reason = get_single_error_reason(&errors, span); + assert!(matches!(reason, ParserErrorReason::NoFunctionAttributesAllowedOnStruct)); + } + + #[test] + fn recovers_on_non_field() { + let src = " + struct Foo { 42 x: i32 } + ^^ + "; + let (src, span) = get_source_with_error_span(src); + let (module, errors) = parse_program(&src); + + assert_eq!(module.items.len(), 1); + let item = &module.items[0]; + let ItemKind::Struct(noir_struct) = &item.kind else { + panic!("Expected struct"); + }; + assert_eq!("Foo", noir_struct.name.to_string()); + assert_eq!(noir_struct.fields.len(), 1); + + let error = get_single_error(&errors, span); + assert_eq!(error.to_string(), "Expected an identifier but found 42"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/test_helpers.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/test_helpers.rs deleted file mode 100644 index 83c1e148f0e..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/test_helpers.rs +++ /dev/null @@ -1,122 +0,0 @@ -use chumsky::primitive::just; -use chumsky::Parser; -use iter_extended::vecmap; -use noirc_errors::CustomDiagnostic; - -use crate::{ - lexer::Lexer, - parser::{force, NoirParser}, - token::Token, -}; - -pub(crate) fn parse_with(parser: P, program: &str) -> Result> -where - P: NoirParser, -{ - let (tokens, lexer_errors) = Lexer::lex(program); - if !lexer_errors.is_empty() { - return Err(vecmap(&lexer_errors, Into::into)); - } - parser.then_ignore(just(Token::EOF)).parse(tokens).map_err(|errors| vecmap(&errors, Into::into)) -} - -pub(crate) fn parse_recover(parser: P, program: &str) -> (Option, Vec) -where - P: NoirParser, -{ - let (tokens, lexer_errors) = Lexer::lex(program); - let (opt, errs) = parser.then_ignore(force(just(Token::EOF))).parse_recovery(tokens); - - let mut errors = vecmap(&lexer_errors, Into::into); - errors.extend(errs.iter().map(Into::into)); - - (opt, errors) -} - -pub(crate) fn parse_all(parser: P, programs: Vec<&str>) -> Vec -where - P: NoirParser, -{ - vecmap(programs, move |program| { - let message = format!("Failed to parse:\n{program}"); - let (op_t, diagnostics) = parse_recover(&parser, program); - diagnostics.iter().for_each(|diagnostic| { - if diagnostic.is_error() { - panic!("{} with error {}", &message, diagnostic); - } - }); - op_t.expect(&message) - }) -} - -pub(crate) fn parse_all_failing(parser: P, programs: Vec<&str>) -> Vec -where - P: NoirParser, - T: std::fmt::Display, -{ - programs - .into_iter() - .flat_map(|program| match parse_with(&parser, program) { - Ok(expr) => { - unreachable!( - "Expected this input to fail:\n{}\nYet it successfully parsed as:\n{}", - program, expr - ) - } - Err(diagnostics) => { - if diagnostics.iter().all(|diagnostic: &CustomDiagnostic| diagnostic.is_warning()) { - unreachable!( - "Expected at least one error when parsing:\n{}\nYet it successfully parsed without errors:\n", - program - ) - }; - diagnostics - } - }) - .collect() -} - -#[derive(Copy, Clone)] -pub(crate) struct Case { - pub(crate) source: &'static str, - pub(crate) errors: usize, - pub(crate) expect: &'static str, -} - -pub(crate) fn check_cases_with_errors(cases: &[Case], parser: P) -where - P: NoirParser + Clone, - T: std::fmt::Display, -{ - let show_errors = |v| vecmap(&v, ToString::to_string).join("\n"); - - let results = vecmap(cases, |&case| { - let (opt, errors) = parse_recover(parser.clone(), case.source); - let actual = opt.map(|ast| ast.to_string()); - let actual = if let Some(s) = &actual { s.to_string() } else { "(none)".to_string() }; - - let result = ((errors.len(), actual.clone()), (case.errors, case.expect.to_string())); - if result.0 != result.1 { - let num_errors = errors.len(); - let shown_errors = show_errors(errors); - eprintln!( - concat!( - "\nExpected {expected_errors} error(s) and got {num_errors}:", - "\n\n{shown_errors}", - "\n\nFrom input: {src}", - "\nExpected AST: {expected_result}", - "\nActual AST: {actual}\n", - ), - expected_errors = case.errors, - num_errors = num_errors, - shown_errors = shown_errors, - src = case.source, - expected_result = case.expect, - actual = actual, - ); - } - result - }); - - assert_eq!(vecmap(&results, |t| t.0.clone()), vecmap(&results, |t| t.1.clone()),); -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/tests.rs new file mode 100644 index 00000000000..ea8b1fc638d --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/tests.rs @@ -0,0 +1,55 @@ +#![cfg(test)] + +use noirc_errors::Span; + +use crate::parser::{ParserError, ParserErrorReason}; + +pub(super) fn get_source_with_error_span(src: &str) -> (String, Span) { + let mut lines: Vec<&str> = src.trim_end().lines().collect(); + let squiggles_line = lines.pop().expect("Expected at least two lines in src (the last one should have squiggles for the error location)"); + let squiggle_index = squiggles_line + .chars() + .position(|char| char == '^') + .expect("Expected at least one `^` character in the last line of the src"); + let squiggle_length = squiggles_line.len() - squiggle_index; + let last_line = lines.last().expect("Expected at least two lines in src"); + let src = lines.join("\n"); + let span_start = src.len() - last_line.len() + squiggle_index; + let span_end = span_start + squiggle_length; + let span = Span::from(span_start as u32..span_end as u32); + (src, span) +} + +pub(super) fn get_single_error(errors: &[ParserError], expected_span: Span) -> &ParserError { + if errors.is_empty() { + panic!("Expected an error, found none"); + } + + if errors.len() > 1 { + for error in errors { + println!("{}", error); + } + panic!("Expected one error, found {} errors (printed above)", errors.len()); + } + + assert_eq!(errors[0].span(), expected_span); + &errors[0] +} + +pub(super) fn get_single_error_reason( + errors: &[ParserError], + expected_span: Span, +) -> &ParserErrorReason { + get_single_error(errors, expected_span).reason().unwrap() +} + +pub(super) fn expect_no_errors(errors: &[ParserError]) { + if errors.is_empty() { + return; + } + + for error in errors { + println!("{}", error); + } + panic!("Expected no errors, found {} errors (printed above)", errors.len()); +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs index 78453d7f7a2..3bae152e75f 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/traits.rs @@ -1,342 +1,295 @@ -use chumsky::prelude::*; - -use super::attributes::{attributes, validate_secondary_attributes}; -use super::doc_comments::outer_doc_comments; -use super::function::{function_modifiers, function_return_type}; -use super::path::path_no_turbofish; -use super::visibility::item_visibility; -use super::{ - block, expression, fresh_statement, function, function_declaration_parameters, let_statement, -}; +use noirc_errors::Span; -use crate::ast::{ - Documented, Expression, ItemVisibility, NoirTrait, NoirTraitImpl, Pattern, TraitBound, - TraitImplItem, TraitImplItemKind, TraitItem, UnresolvedTraitConstraint, UnresolvedType, -}; -use crate::parser::spanned; +use crate::ast::{Documented, ItemVisibility, NoirTrait, Pattern, TraitItem, UnresolvedType}; use crate::{ - parser::{ - ignore_then_commit, parenthesized, parser::primitives::keyword, NoirParser, ParserError, - ParserErrorReason, TopLevelStatementKind, - }, - token::{Keyword, Token}, + ast::{Ident, UnresolvedTypeData}, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Attribute, Keyword, SecondaryAttribute, Token}, }; -use super::{generic_type_args, parse_type, primitives::ident}; - -pub(super) fn trait_definition() -> impl NoirParser { - let trait_body_or_error = just(Token::LeftBrace) - .ignore_then(trait_body()) - .then_ignore(just(Token::RightBrace)) - .or_not() - .validate(|items, span, emit| { - if let Some(items) = items { - items - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitName, - span, - )); - vec![] - } - }); - - attributes() - .then(item_visibility()) - .then_ignore(keyword(Keyword::Trait)) - .then(ident()) - .then(function::generics()) - .then(where_clause()) - .then(trait_body_or_error) - .validate( - |(((((attributes, visibility), name), generics), where_clause), items), span, emit| { - let attributes = validate_secondary_attributes(attributes, span, emit); - TopLevelStatementKind::Trait(NoirTrait { - name, - generics, - where_clause, - span, - items, - attributes, - visibility, - }) - }, - ) -} - -fn trait_body() -> impl NoirParser>> { - let item = - trait_function_declaration().or(trait_type_declaration()).or(trait_constant_declaration()); - outer_doc_comments() - .then(item) - .map(|(doc_comments, item)| Documented::new(item, doc_comments)) - .repeated() -} +use super::parse_many::without_separator; +use super::Parser; + +impl<'a> Parser<'a> { + /// Trait = 'trait' identifier Generics WhereClause TraitBody + pub(crate) fn parse_trait( + &mut self, + attributes: Vec<(Attribute, Span)>, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirTrait { + let attributes = self.validate_secondary_attributes(attributes); + + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return empty_trait(attributes, visibility, self.span_since(start_span)); + }; -fn optional_default_value() -> impl NoirParser> { - ignore_then_commit(just(Token::Assign), expression()).or_not() -} + let generics = self.parse_generics(); + let where_clause = self.parse_where_clause(); + let items = self.parse_trait_body(); + + NoirTrait { + name, + generics, + where_clause, + span: self.span_since(start_span), + items, + attributes, + visibility, + } + } -fn trait_constant_declaration() -> impl NoirParser { - keyword(Keyword::Let) - .ignore_then(ident()) - .then_ignore(just(Token::Colon)) - .then(parse_type()) - .then(optional_default_value()) - .then_ignore(just(Token::Semicolon)) - .map(|((name, typ), default_value)| TraitItem::Constant { name, typ, default_value }) -} + /// TraitBody = '{' ( OuterDocComments TraitItem )* '}' + fn parse_trait_body(&mut self) -> Vec> { + if !self.eat_left_brace() { + self.expected_token(Token::LeftBrace); + return Vec::new(); + } -/// trait_function_declaration: 'fn' ident generics '(' declaration_parameters ')' function_return_type -fn trait_function_declaration() -> impl NoirParser { - let trait_function_body_or_semicolon = - block(fresh_statement()).map(Option::from).or(just(Token::Semicolon).to(Option::None)); - - let trait_function_body_or_semicolon_or_error = - trait_function_body_or_semicolon.or_not().validate(|body, span, emit| { - if let Some(body) = body { - body - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBraceOrArrowAfterFunctionParameters, - span, - )); - None - } - }); - - function_modifiers() - .then_ignore(keyword(Keyword::Fn)) - .then(ident()) - .then(function::generics()) - .then(parenthesized(function_declaration_parameters())) - .then(function_return_type().map(|(_, typ)| typ)) - .then(where_clause()) - .then(trait_function_body_or_semicolon_or_error) - .map( - |((((((modifiers, name), generics), parameters), return_type), where_clause), body)| { - TraitItem::Function { - name, - generics, - parameters, - return_type, - where_clause, - body, - is_unconstrained: modifiers.0, - visibility: modifiers.1, - is_comptime: modifiers.2, - } - }, + self.parse_many( + "trait items", + without_separator().until(Token::RightBrace), + Self::parse_trait_item_in_list, ) -} + } -/// trait_type_declaration: 'type' ident generics -fn trait_type_declaration() -> impl NoirParser { - keyword(Keyword::Type) - .ignore_then(ident()) - .then_ignore(just(Token::Semicolon)) - .map(|name| TraitItem::Type { name }) -} + fn parse_trait_item_in_list(&mut self) -> Option> { + self.parse_item_in_list(ParsingRuleLabel::TraitItem, |parser| { + let doc_comments = parser.parse_outer_doc_comments(); + parser.parse_trait_item().map(|item| Documented::new(item, doc_comments)) + }) + } -/// Parses a trait implementation, implementing a particular trait for a type. -/// This has a similar syntax to `implementation`, but the `for type` clause is required, -/// and an optional `where` clause is also useable. -/// -/// trait_implementation: 'impl' generics ident generic_args for type '{' trait_implementation_body '}' -pub(super) fn trait_implementation() -> impl NoirParser { - let body_or_error = - just(Token::LeftBrace) - .ignore_then(trait_implementation_body()) - .then_ignore(just(Token::RightBrace)) - .or_not() - .validate(|items, span, emit| { - if let Some(items) = items { - items - } else { - emit(ParserError::with_reason( - ParserErrorReason::ExpectedLeftBracketOrWhereOrLeftBraceOrArrowAfterTraitImplForType, - span, - )); + /// TraitItem + /// = TraitType + /// | TraitConstant + /// | TraitFunction + fn parse_trait_item(&mut self) -> Option { + if let Some(item) = self.parse_trait_type() { + return Some(item); + } - vec![] - } - }); - - keyword(Keyword::Impl) - .ignore_then(function::generics()) - .then(path_no_turbofish()) - .then(generic_type_args(parse_type())) - .then_ignore(keyword(Keyword::For)) - .then(parse_type()) - .then(where_clause()) - .then(body_or_error) - .map(|args| { - let (((other_args, object_type), where_clause), items) = args; - let ((impl_generics, trait_name), trait_generics) = other_args; - TopLevelStatementKind::TraitImpl(NoirTraitImpl { - impl_generics, - trait_name, - trait_generics, - object_type, - items, - where_clause, - }) - }) -} + if let Some(item) = self.parse_trait_constant() { + return Some(item); + } -fn trait_implementation_body() -> impl NoirParser>> { - let function = function::function_definition(true).validate(|mut f, span, emit| { - if f.def().visibility != ItemVisibility::Private { - emit(ParserError::with_reason(ParserErrorReason::TraitImplVisibilityIgnored, span)); + if let Some(item) = self.parse_trait_function() { + return Some(item); } - // Trait impl functions are always public - f.def_mut().visibility = ItemVisibility::Public; - TraitImplItemKind::Function(f) - }); - - let alias = keyword(Keyword::Type) - .ignore_then(ident()) - .then_ignore(just(Token::Assign)) - .then(parse_type()) - .then_ignore(just(Token::Semicolon)) - .map(|(name, alias)| TraitImplItemKind::Type { name, alias }); - - let let_statement = let_statement(expression()).then_ignore(just(Token::Semicolon)).try_map( - |((pattern, typ), expr), span| match pattern { - Pattern::Identifier(ident) => Ok(TraitImplItemKind::Constant(ident, typ, expr)), - _ => Err(ParserError::with_reason( - ParserErrorReason::PatternInTraitFunctionParameter, - span, - )), - }, - ); - let item = choice((function, alias, let_statement)); - outer_doc_comments() - .then(spanned(item).map(|(kind, span)| TraitImplItem { kind, span })) - .map(|(doc_comments, item)| Documented::new(item, doc_comments)) - .repeated() -} + None + } -pub(super) fn where_clause() -> impl NoirParser> { - struct MultiTraitConstraint { - typ: UnresolvedType, - trait_bounds: Vec, + /// TraitType = 'type' identifier ';' + fn parse_trait_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::Type) { + return None; + } + + let name = match self.eat_ident() { + Some(name) => name, + None => { + self.expected_identifier(); + Ident::default() + } + }; + + self.eat_semicolons(); + + Some(TraitItem::Type { name }) } - let constraints = parse_type() - .then_ignore(just(Token::Colon)) - .then(trait_bounds()) - .map(|(typ, trait_bounds)| MultiTraitConstraint { typ, trait_bounds }); - - keyword(Keyword::Where) - .ignore_then(constraints.separated_by(just(Token::Comma)).allow_trailing()) - .or_not() - .map(|option| option.unwrap_or_default()) - .map(|x: Vec| { - let mut result: Vec = Vec::new(); - for constraint in x { - for bound in constraint.trait_bounds { - result.push(UnresolvedTraitConstraint { - typ: constraint.typ.clone(), - trait_bound: bound, - }); - } + /// TraitConstant = 'let' identifier ':' Type ( '=' Expression ) ';' + fn parse_trait_constant(&mut self) -> Option { + if !self.eat_keyword(Keyword::Let) { + return None; + } + + let name = match self.eat_ident() { + Some(name) => name, + None => { + self.expected_identifier(); + Ident::default() } - result - }) -} + }; -fn trait_bounds() -> impl NoirParser> { - trait_bound().separated_by(just(Token::Plus)).at_least(1).allow_trailing() -} + let typ = if self.eat_colon() { + self.parse_type_or_error() + } else { + self.expected_token(Token::Colon); + UnresolvedType { typ: UnresolvedTypeData::Unspecified, span: Span::default() } + }; -pub fn trait_bound() -> impl NoirParser { - path_no_turbofish().then(generic_type_args(parse_type())).map(|(trait_path, trait_generics)| { - TraitBound { trait_path, trait_generics, trait_id: None } - }) -} + let default_value = + if self.eat_assign() { Some(self.parse_expression_or_error()) } else { None }; -#[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; + self.eat_semicolons(); - #[test] - fn parse_trait() { - parse_all( - trait_definition(), - vec![ - // Empty traits are legal in Rust and sometimes used as a way to whitelist certain types - // for a particular operation. Also known as `tag` or `marker` traits: - // https://stackoverflow.com/questions/71895489/what-is-the-purpose-of-defining-empty-impl-in-rust - "trait Empty {}", - "trait TraitWithDefaultBody { fn foo(self) {} }", - "trait TraitAcceptingMutableRef { fn foo(&mut self); }", - "trait TraitWithTypeBoundOperation { fn identity() -> Self; }", - "trait TraitWithAssociatedType { type Element; fn item(self, index: Field) -> Self::Element; }", - "trait TraitWithAssociatedConstant { let Size: Field; }", - "trait TraitWithAssociatedConstantWithDefaultValue { let Size: Field = 10; }", - "trait GenericTrait { fn elem(&mut self, index: Field) -> T; }", - "trait GenericTraitWithConstraints where T: SomeTrait { fn elem(self, index: Field) -> T; }", - "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait { let Size: Field; fn zero() -> Self; }", - "trait TraitWithMultipleGenericParams where A: SomeTrait, B: AnotherTrait, { let Size: Field; fn zero() -> Self; }", - ], + Some(TraitItem::Constant { name, typ, default_value }) + } + + /// TraitFunction = Modifiers Function + fn parse_trait_function(&mut self) -> Option { + let modifiers = self.parse_modifiers( + false, // allow mut ); - parse_all_failing( - trait_definition(), - vec!["trait MissingBody", "trait WrongDelimiter { fn foo() -> u8, fn bar() -> u8 }"], + if !self.eat_keyword(Keyword::Fn) { + self.modifiers_not_followed_by_an_item(modifiers); + return None; + } + + let function = self.parse_function_definition_with_optional_body( + true, // allow optional body + true, // allow self ); - } - #[test] - fn parse_recover_function_without_left_brace_or_semicolon() { - let src = "fn foo(x: i32)"; + let parameters = function + .parameters + .into_iter() + .filter_map(|param| { + if let Pattern::Identifier(ident) = param.pattern { + Some((ident, param.typ)) + } else { + self.push_error(ParserErrorReason::InvalidPattern, param.pattern.span()); + None + } + }) + .collect(); + + Some(TraitItem::Function { + is_unconstrained: modifiers.unconstrained.is_some(), + visibility: modifiers.visibility, + is_comptime: modifiers.comptime.is_some(), + name: function.name, + generics: function.generics, + parameters, + return_type: function.return_type, + where_clause: function.where_clause, + body: function.body, + }) + } +} - let (trait_item, errors) = parse_recover(trait_function_declaration(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected { or -> after function parameters"); +fn empty_trait( + attributes: Vec, + visibility: ItemVisibility, + span: Span, +) -> NoirTrait { + NoirTrait { + name: Ident::default(), + generics: Vec::new(), + where_clause: Vec::new(), + span, + items: Vec::new(), + attributes, + visibility, + } +} - let Some(TraitItem::Function { name, parameters, body, .. }) = trait_item else { - panic!("Expected to parser trait item as function"); +#[cfg(test)] +mod tests { + use crate::{ + ast::{NoirTrait, TraitItem}, + parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }, + }; + + fn parse_trait_no_errors(src: &str) -> NoirTrait { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Trait(noir_trait) = item.kind else { + panic!("Expected trait"); }; + noir_trait + } - assert_eq!(name.to_string(), "foo"); - assert_eq!(parameters.len(), 1); - assert!(body.is_none()); + #[test] + fn parse_empty_trait() { + let src = "trait Foo {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert!(noir_trait.generics.is_empty()); + assert!(noir_trait.where_clause.is_empty()); + assert!(noir_trait.items.is_empty()); } #[test] - fn parse_recover_trait_without_body() { - let src = "trait Foo"; + fn parse_trait_with_generics() { + let src = "trait Foo {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert_eq!(noir_trait.generics.len(), 2); + assert!(noir_trait.where_clause.is_empty()); + assert!(noir_trait.items.is_empty()); + } - let (top_level_statement, errors) = parse_recover(trait_definition(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected <, where or { after trait name"); + #[test] + fn parse_trait_with_where_clause() { + let src = "trait Foo where A: Z {}"; + let noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.name.to_string(), "Foo"); + assert_eq!(noir_trait.generics.len(), 2); + assert_eq!(noir_trait.where_clause.len(), 1); + assert!(noir_trait.items.is_empty()); + } - let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatementKind::Trait(trait_) = top_level_statement else { - panic!("Expected to parse a trait"); + #[test] + fn parse_trait_with_type() { + let src = "trait Foo { type Elem; }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Type { name } = item else { + panic!("Expected type"); }; - - assert_eq!(trait_.name.to_string(), "Foo"); - assert!(trait_.items.is_empty()); + assert_eq!(name.to_string(), "Elem"); } #[test] - fn parse_recover_trait_impl_without_body() { - let src = "impl Foo for Bar"; - - let (top_level_statement, errors) = parse_recover(trait_implementation(), src); - assert_eq!(errors.len(), 1); - assert_eq!(errors[0].message, "expected <, where or { after trait impl for type"); + fn parse_trait_with_constant() { + let src = "trait Foo { let x: Field = 1; }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Constant { name, typ, default_value } = item else { + panic!("Expected constant"); + }; + assert_eq!(name.to_string(), "x"); + assert_eq!(typ.to_string(), "Field"); + assert_eq!(default_value.unwrap().to_string(), "1"); + } - let top_level_statement = top_level_statement.unwrap(); - let TopLevelStatementKind::TraitImpl(trait_impl) = top_level_statement else { - panic!("Expected to parse a trait impl"); + #[test] + fn parse_trait_with_function_no_body() { + let src = "trait Foo { fn foo(); }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Function { body, .. } = item else { + panic!("Expected function"); }; + assert!(body.is_none()); + } - assert!(trait_impl.items.is_empty()); + #[test] + fn parse_trait_with_function_with_body() { + let src = "trait Foo { fn foo() {} }"; + let mut noir_trait = parse_trait_no_errors(src); + assert_eq!(noir_trait.items.len(), 1); + + let item = noir_trait.items.remove(0).item; + let TraitItem::Function { body, .. } = item else { + panic!("Expected function"); + }; + assert!(body.is_some()); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_alias.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_alias.rs new file mode 100644 index 00000000000..52815dc3783 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_alias.rs @@ -0,0 +1,92 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, ItemVisibility, NoirTypeAlias, UnresolvedType, UnresolvedTypeData}, + token::Token, +}; + +use super::Parser; + +impl<'a> Parser<'a> { + /// TypeAlias = 'type' identifier Generics '=' Type ';' + pub(crate) fn parse_type_alias( + &mut self, + visibility: ItemVisibility, + start_span: Span, + ) -> NoirTypeAlias { + let Some(name) = self.eat_ident() else { + self.expected_identifier(); + return NoirTypeAlias { + visibility, + name: Ident::default(), + generics: Vec::new(), + typ: UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }, + span: start_span, + }; + }; + + let generics = self.parse_generics(); + + if !self.eat_assign() { + self.expected_token(Token::Assign); + + let span = self.span_since(start_span); + self.eat_semicolons(); + + return NoirTypeAlias { + visibility, + name, + generics, + typ: UnresolvedType { typ: UnresolvedTypeData::Error, span: Span::default() }, + span, + }; + } + + let typ = self.parse_type_or_error(); + let span = self.span_since(start_span); + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + + NoirTypeAlias { visibility, name, generics, typ, span } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{NoirTypeAlias, UnresolvedTypeData}, + parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }, + }; + + fn parse_type_alias_no_errors(src: &str) -> NoirTypeAlias { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::TypeAlias(alias) = item.kind else { + panic!("Expected global"); + }; + alias + } + + #[test] + fn parse_type_alias_no_generics() { + let src = "type Foo = Field;"; + let alias = parse_type_alias_no_errors(src); + assert_eq!("Foo", alias.name.to_string()); + assert!(alias.generics.is_empty()); + assert_eq!(alias.typ.typ, UnresolvedTypeData::FieldElement); + } + + #[test] + fn parse_type_alias_with_generics() { + let src = "type Foo = Field;"; + let alias = parse_type_alias_no_errors(src); + assert_eq!("Foo", alias.name.to_string()); + assert_eq!(alias.generics.len(), 1); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs new file mode 100644 index 00000000000..c3f27d9d49a --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/type_expression.rs @@ -0,0 +1,637 @@ +use crate::{ + ast::{ + Expression, ExpressionKind, GenericTypeArgs, Literal, UnresolvedType, UnresolvedTypeData, + UnresolvedTypeExpression, + }, + parser::{labels::ParsingRuleLabel, ParserError, ParserErrorReason}, + token::Token, + BinaryTypeOperator, +}; + +use acvm::acir::AcirField; +use noirc_errors::Span; + +use super::{parse_many::separated_by_comma_until_right_paren, Parser}; + +impl<'a> Parser<'a> { + /// TypeExpression= AddOrSubtractTypeExpression + pub(crate) fn parse_type_expression( + &mut self, + ) -> Result { + match self.parse_add_or_subtract_type_expression() { + Some(type_expr) => Ok(type_expr), + None => self.expected_type_expression_after_this(), + } + } + + /// AddOrSubtractTypeExpression + /// = MultiplyOrDivideOrModuloTypeExpression ( ( '+' | '-' ) MultiplyOrDivideOrModuloTypeExpression )* + fn parse_add_or_subtract_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_multiply_or_divide_or_modulo_type_expression()?; + Some(self.parse_add_or_subtract_type_expression_after_lhs(lhs, start_span)) + } + + fn parse_add_or_subtract_type_expression_after_lhs( + &mut self, + mut lhs: UnresolvedTypeExpression, + start_span: Span, + ) -> UnresolvedTypeExpression { + loop { + let operator = if self.eat(Token::Plus) { + BinaryTypeOperator::Addition + } else if self.eat(Token::Minus) { + BinaryTypeOperator::Subtraction + } else { + break; + }; + + match self.parse_multiply_or_divide_or_modulo_type_expression() { + Some(rhs) => { + let span = self.span_since(start_span); + lhs = UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + operator, + Box::new(rhs), + span, + ); + } + None => { + self.push_expected_expression(); + } + } + } + + lhs + } + + /// MultiplyOrDivideOrModuloTypeExpression + /// = TermTypeExpression ( ( '*' | '/' | '%' ) TermTypeExpression )* + fn parse_multiply_or_divide_or_modulo_type_expression( + &mut self, + ) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_term_type_expression()?; + Some(self.parse_multiply_or_divide_or_modulo_type_expression_after_lhs(lhs, start_span)) + } + + fn parse_multiply_or_divide_or_modulo_type_expression_after_lhs( + &mut self, + mut lhs: UnresolvedTypeExpression, + start_span: Span, + ) -> UnresolvedTypeExpression { + loop { + let operator = if self.eat(Token::Star) { + BinaryTypeOperator::Multiplication + } else if self.eat(Token::Slash) { + BinaryTypeOperator::Division + } else if self.eat(Token::Percent) { + BinaryTypeOperator::Modulo + } else { + break; + }; + + match self.parse_term_type_expression() { + Some(rhs) => { + let span = self.span_since(start_span); + lhs = UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + operator, + Box::new(rhs), + span, + ); + } + None => { + self.push_expected_expression(); + break; + } + } + } + + lhs + } + + /// TermTypeExpression + /// = '- TermTypeExpression + /// | AtomTypeExpression + fn parse_term_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + if self.eat(Token::Minus) { + return match self.parse_term_type_expression() { + Some(rhs) => { + let lhs = UnresolvedTypeExpression::Constant(0, start_span); + let op = BinaryTypeOperator::Subtraction; + let span = self.span_since(start_span); + Some(UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + op, + Box::new(rhs), + span, + )) + } + None => { + self.push_expected_expression(); + None + } + }; + } + + self.parse_atom_type_expression() + } + + /// AtomTypeExpression + /// = ConstantTypeExpression + /// | VariableTypeExpression + /// | ParenthesizedTypeExpression + fn parse_atom_type_expression(&mut self) -> Option { + if let Some(type_expr) = self.parse_constant_type_expression() { + return Some(type_expr); + } + + if let Some(type_expr) = self.parse_variable_type_expression() { + return Some(type_expr); + } + + if let Some(type_expr) = self.parse_parenthesized_type_expression() { + return Some(type_expr); + } + + None + } + + /// ConstantTypeExpression = int + fn parse_constant_type_expression(&mut self) -> Option { + let Some(int) = self.eat_int() else { + return None; + }; + + let int = if let Some(int) = int.try_to_u32() { + int + } else { + let err_expr = Expression { + kind: ExpressionKind::Literal(Literal::Integer(int, false)), + span: self.previous_token_span, + }; + self.push_error( + ParserErrorReason::InvalidTypeExpression(err_expr), + self.previous_token_span, + ); + 0 + }; + + Some(UnresolvedTypeExpression::Constant(int, self.previous_token_span)) + } + + /// VariableTypeExpression = Path + fn parse_variable_type_expression(&mut self) -> Option { + let path = self.parse_path()?; + Some(UnresolvedTypeExpression::Variable(path)) + } + + /// ParenthesizedTypeExpression = '(' TypeExpression ')' + fn parse_parenthesized_type_expression(&mut self) -> Option { + // Make sure not to parse `()` as a parenthesized expression + if self.at(Token::LeftParen) && !self.next_is(Token::RightParen) { + self.bump(); + match self.parse_type_expression() { + Ok(type_expr) => { + self.eat_or_error(Token::RightParen); + Some(type_expr) + } + Err(error) => { + self.errors.push(error); + self.eat_right_paren(); + None + } + } + } else { + None + } + } + + /// TypeOrTypeExpression = Type | TypeExpression + pub(crate) fn parse_type_or_type_expression(&mut self) -> Option { + let typ = self.parse_add_or_subtract_type_or_type_expression()?; + let span = typ.span; + + // If we end up with a Variable type expression, make it a Named type (they are equivalent), + // but for testing purposes and simplicity we default to types instead of type expressions. + Some( + if let UnresolvedTypeData::Expression(UnresolvedTypeExpression::Variable(path)) = + typ.typ + { + UnresolvedType { + typ: UnresolvedTypeData::Named(path, GenericTypeArgs::default(), false), + span, + } + } else { + typ + }, + ) + } + + fn parse_add_or_subtract_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_multiply_or_divide_or_modulo_type_or_type_expression()?; + + // If lhs is a type then no operator can follow, so we stop right away + if !type_is_type_expr(&lhs) { + return Some(lhs); + } + + let lhs = type_to_type_expr(lhs).unwrap(); + let lhs = self.parse_add_or_subtract_type_expression_after_lhs(lhs, start_span); + Some(type_expr_to_type(lhs, self.span_since(start_span))) + } + + fn parse_multiply_or_divide_or_modulo_type_or_type_expression( + &mut self, + ) -> Option { + let start_span = self.current_token_span; + let lhs = self.parse_term_type_or_type_expression()?; + + // If lhs is a type then no operator can follow, so we stop right away + if !type_is_type_expr(&lhs) { + return Some(lhs); + } + + let lhs = type_to_type_expr(lhs).unwrap(); + let lhs = + self.parse_multiply_or_divide_or_modulo_type_expression_after_lhs(lhs, start_span); + Some(type_expr_to_type(lhs, self.span_since(start_span))) + } + + fn parse_term_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + if self.eat(Token::Minus) { + // If we ate '-' what follows must be a type expression, never a type + return match self.parse_term_type_expression() { + Some(rhs) => { + let lhs = UnresolvedTypeExpression::Constant(0, start_span); + let op = BinaryTypeOperator::Subtraction; + let span = self.span_since(start_span); + let type_expr = UnresolvedTypeExpression::BinaryOperation( + Box::new(lhs), + op, + Box::new(rhs), + span, + ); + let typ = UnresolvedTypeData::Expression(type_expr); + Some(UnresolvedType { typ, span }) + } + None => { + self.push_expected_expression(); + None + } + }; + } + + self.parse_atom_type_or_type_expression() + } + + fn parse_atom_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + + if let Some(path) = self.parse_path() { + let generics = self.parse_generic_type_args(); + let typ = UnresolvedTypeData::Named(path, generics, false); + let span = self.span_since(start_span); + return Some(UnresolvedType { typ, span }); + } + + if let Some(type_expr) = self.parse_constant_type_expression() { + let typ = UnresolvedTypeData::Expression(type_expr); + let span = self.span_since(start_span); + return Some(UnresolvedType { typ, span }); + } + + if let Some(typ) = self.parse_parenthesized_type_or_type_expression() { + return Some(typ); + } + + self.parse_type() + } + + fn parse_parenthesized_type_or_type_expression(&mut self) -> Option { + let start_span = self.current_token_span; + + if !self.eat_left_paren() { + return None; + } + + if self.eat_right_paren() { + return Some(UnresolvedType { + typ: UnresolvedTypeData::Unit, + span: self.span_since(start_span), + }); + } + + let Some(typ) = self.parse_type_or_type_expression() else { + self.expected_label(ParsingRuleLabel::TypeOrTypeExpression); + return None; + }; + + // If what we just parsed is a type expression then this must be a parenthesized type + // expression (there's no such thing as a tuple of type expressions) + if let UnresolvedTypeData::Expression(type_expr) = typ.typ { + self.eat_or_error(Token::RightParen); + return Some(UnresolvedType { + typ: UnresolvedTypeData::Expression(type_expr), + span: typ.span, + }); + } + + if self.eat_right_paren() { + return Some(UnresolvedType { + typ: UnresolvedTypeData::Parenthesized(Box::new(typ)), + span: self.span_since(start_span), + }); + } + + let comma_after_first_type = self.eat_comma(); + let second_type_span = self.current_token_span; + + let mut types = self.parse_many( + "tuple items", + separated_by_comma_until_right_paren(), + Self::parse_type_in_list, + ); + + if !types.is_empty() && !comma_after_first_type { + self.expected_token_separating_items(Token::Comma, "tuple items", second_type_span); + } + + types.insert(0, typ); + + Some(UnresolvedType { + typ: UnresolvedTypeData::Tuple(types), + span: self.span_since(start_span), + }) + } + + fn expected_type_expression_after_this( + &mut self, + ) -> Result { + Err(ParserError::expected_label( + ParsingRuleLabel::TypeExpression, + self.token.token().clone(), + self.current_token_span, + )) + } +} + +fn type_to_type_expr(typ: UnresolvedType) -> Option { + match typ.typ { + UnresolvedTypeData::Named(var, generics, _) => { + if generics.is_empty() { + Some(UnresolvedTypeExpression::Variable(var)) + } else { + None + } + } + UnresolvedTypeData::Expression(type_expr) => Some(type_expr), + _ => None, + } +} + +fn type_is_type_expr(typ: &UnresolvedType) -> bool { + match &typ.typ { + UnresolvedTypeData::Named(_, generics, _) => generics.is_empty(), + UnresolvedTypeData::Expression(..) => true, + _ => false, + } +} + +fn type_expr_to_type(lhs: UnresolvedTypeExpression, span: Span) -> UnresolvedType { + let lhs = UnresolvedTypeData::Expression(lhs); + UnresolvedType { typ: lhs, span } +} + +#[cfg(test)] +mod tests { + use core::panic; + + use crate::{ + ast::{UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::Token, + BinaryTypeOperator, + }; + + fn parse_type_expression_no_errors(src: &str) -> UnresolvedTypeExpression { + let mut parser = Parser::for_str(src); + let expr = parser.parse_type_expression().unwrap(); + expect_no_errors(&parser.errors); + expr + } + + fn parse_type_or_type_expression_no_errors(src: &str) -> UnresolvedType { + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_type_expression().unwrap(); + expect_no_errors(&parser.errors); + typ + } + + #[test] + fn parses_constant_type_expression() { + let src = "42"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::Constant(n, _) = expr else { + panic!("Expected constant"); + }; + assert_eq!(n, 42); + } + + #[test] + fn parses_variable_type_expression() { + let src = "foo::bar"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::Variable(path) = expr else { + panic!("Expected path"); + }; + assert_eq!(path.to_string(), "foo::bar"); + } + + #[test] + fn parses_binary_type_expression() { + let src = "1 + 2 * 3 + 4"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::BinaryOperation(lhs, operator, rhs, _) = expr else { + panic!("Expected binary operation"); + }; + assert_eq!(lhs.to_string(), "(1 + (2 * 3))"); + assert_eq!(operator, BinaryTypeOperator::Addition); + assert_eq!(rhs.to_string(), "4"); + } + + #[test] + fn parses_parenthesized_type_expression() { + let src = "(N)"; + let expr = parse_type_expression_no_errors(src); + let UnresolvedTypeExpression::Variable(path) = expr else { + panic!("Expected variable"); + }; + assert_eq!(path.to_string(), "N"); + } + + #[test] + fn parses_minus_type_expression() { + let src = "-N"; + let expr = parse_type_expression_no_errors(src); + assert_eq!(expr.to_string(), "(0 - N)"); + } + + #[test] + fn parse_type_or_type_expression_constant() { + let src = "42"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression"); + }; + let UnresolvedTypeExpression::Constant(n, _) = expr else { + panic!("Expected constant"); + }; + assert_eq!(n, 42); + } + + #[test] + fn parse_type_or_type_expression_variable() { + let src = "foo::Bar"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Named(path, generics, _) = typ.typ else { + panic!("Expected named type"); + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(generics.is_empty()); + } + + #[test] + fn parses_type_or_type_expression_binary() { + let src = "1 + 2 * 3 + 4"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression"); + }; + let UnresolvedTypeExpression::BinaryOperation(lhs, operator, rhs, _) = expr else { + panic!("Expected binary operation"); + }; + assert_eq!(lhs.to_string(), "(1 + (2 * 3))"); + assert_eq!(operator, BinaryTypeOperator::Addition); + assert_eq!(rhs.to_string(), "4"); + } + + #[test] + fn parses_type_or_type_expression_minus() { + let src = "-N"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression"); + }; + assert_eq!(expr.to_string(), "(0 - N)"); + } + + #[test] + fn parses_type_or_type_expression_unit() { + let src = "()"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Unit = typ.typ else { + panic!("Expected unit type"); + }; + } + + #[test] + fn parses_type_or_type_expression_parenthesized_type() { + let src = "(Field)"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Parenthesized(typ) = typ.typ else { + panic!("Expected parenthesized type"); + }; + let UnresolvedTypeData::FieldElement = typ.typ else { + panic!("Expected field type"); + }; + } + + #[test] + fn parses_type_or_type_expression_parenthesized_constant() { + let src = "(1)"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression type"); + }; + assert_eq!(expr.to_string(), "1"); + } + + #[test] + fn parses_type_or_type_expression_tuple_type() { + let src = "(Field, bool)"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Tuple(types) = typ.typ else { + panic!("Expected tuple type"); + }; + let UnresolvedTypeData::FieldElement = types[0].typ else { + panic!("Expected field type"); + }; + let UnresolvedTypeData::Bool = types[1].typ else { + panic!("Expected bool type"); + }; + } + + #[test] + fn parses_type_or_type_expression_tuple_type_missing_comma() { + let src = " + (Field bool) + ^^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + + let typ = parser.parse_type_or_type_expression().unwrap(); + + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "tuple items"); + + let UnresolvedTypeData::Tuple(types) = typ.typ else { + panic!("Expected tuple type"); + }; + let UnresolvedTypeData::FieldElement = types[0].typ else { + panic!("Expected field type"); + }; + let UnresolvedTypeData::Bool = types[1].typ else { + panic!("Expected bool type"); + }; + } + + #[test] + fn parses_type_or_type_expression_tuple_type_single_element() { + let src = "(Field,)"; + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_type_expression().unwrap(); + expect_no_errors(&parser.errors); + let UnresolvedTypeData::Tuple(types) = typ.typ else { + panic!("Expected tuple type"); + }; + assert_eq!(types.len(), 1); + let UnresolvedTypeData::FieldElement = types[0].typ else { + panic!("Expected field type"); + }; + } + + #[test] + fn parses_type_or_type_expression_var_minus_one() { + let src = "N - 1"; + let typ = parse_type_or_type_expression_no_errors(src); + let UnresolvedTypeData::Expression(expr) = typ.typ else { + panic!("Expected expression type"); + }; + assert_eq!(expr.to_string(), "(N - 1)"); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs index 9dd41d1a288..6702704d32c 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/types.rs @@ -1,412 +1,679 @@ -use super::path::{as_trait_path, path_no_turbofish}; -use super::primitives::{ident, token_kind}; -use super::{ - expression_with_precedence, keyword, nothing, parenthesized, NoirParser, ParserError, - ParserErrorReason, Precedence, +use crate::{ + ast::{UnresolvedType, UnresolvedTypeData, UnresolvedTypeExpression}, + parser::{labels::ParsingRuleLabel, ParserErrorReason}, + token::{Keyword, Token, TokenKind}, + QuotedType, }; -use crate::ast::{ - Expression, GenericTypeArg, GenericTypeArgs, Recoverable, UnresolvedType, UnresolvedTypeData, - UnresolvedTypeExpression, -}; -use crate::QuotedType; -use crate::parser::labels::ParsingRuleLabel; -use crate::token::{Keyword, Token, TokenKind}; +use super::{parse_many::separated_by_comma_until_right_paren, Parser}; -use chumsky::prelude::*; -use noirc_errors::Span; +impl<'a> Parser<'a> { + pub(crate) fn parse_type_or_error(&mut self) -> UnresolvedType { + if let Some(typ) = self.parse_type() { + typ + } else { + self.expected_label(ParsingRuleLabel::Type); + self.unspecified_type_at_previous_token_end() + } + } -pub fn parse_type<'a>() -> impl NoirParser + 'a { - recursive(parse_type_inner) -} + pub(crate) fn parse_type(&mut self) -> Option { + let start_span = self.current_token_span; + let typ = self.parse_unresolved_type_data()?; + let span = self.span_since(start_span); + Some(UnresolvedType { typ, span }) + } -pub(super) fn parse_type_inner<'a>( - recursive_type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - choice(( - primitive_type(), - format_string_type(recursive_type_parser.clone()), - named_type(recursive_type_parser.clone()), - named_trait(recursive_type_parser.clone()), - slice_type(recursive_type_parser.clone()), - array_type(recursive_type_parser.clone()), - parenthesized_type(recursive_type_parser.clone()), - tuple_type(recursive_type_parser.clone()), - function_type(recursive_type_parser.clone()), - mutable_reference_type(recursive_type_parser.clone()), - as_trait_path_type(recursive_type_parser), - )) -} + fn parse_unresolved_type_data(&mut self) -> Option { + if let Some(typ) = self.parse_primitive_type() { + return Some(typ); + } -pub(super) fn primitive_type() -> impl NoirParser { - choice(( - field_type(), - int_type(), - bool_type(), - string_type(), - comptime_type(), - resolved_type(), - interned_unresolved_type(), - )) -} + if let Some(typ) = self.parse_parentheses_type() { + return Some(typ); + } -fn as_trait_path_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - as_trait_path(type_parser) - .map_with_span(|path, span| UnresolvedTypeData::AsTraitPath(Box::new(path)).with_span(span)) -} + if let Some(typ) = self.parse_array_or_slice_type() { + return Some(typ); + } -pub(super) fn parenthesized_type( - recursive_type_parser: impl NoirParser, -) -> impl NoirParser { - recursive_type_parser - .delimited_by(just(Token::LeftParen), just(Token::RightParen)) - .map_with_span(|typ, span| UnresolvedType { - typ: UnresolvedTypeData::Parenthesized(Box::new(typ)), - span, - }) -} + if let Some(typ) = self.parses_mutable_reference_type() { + return Some(typ); + } -pub(super) fn maybe_comp_time() -> impl NoirParser { - keyword(Keyword::Comptime).or_not().map(|opt| opt.is_some()) -} + if let Some(typ) = self.parse_function_type() { + return Some(typ); + } -pub(super) fn field_type() -> impl NoirParser { - keyword(Keyword::Field) - .map_with_span(|_, span| UnresolvedTypeData::FieldElement.with_span(span)) -} + if let Some(typ) = self.parse_trait_as_type() { + return Some(typ); + } -pub(super) fn bool_type() -> impl NoirParser { - keyword(Keyword::Bool).map_with_span(|_, span| UnresolvedTypeData::Bool.with_span(span)) -} + if let Some(typ) = self.parse_as_trait_path_type() { + return Some(typ); + } -pub(super) fn comptime_type() -> impl NoirParser { - choice(( - expr_type(), - struct_definition_type(), - trait_constraint_type(), - trait_definition_type(), - trait_impl_type(), - unresolved_type_type(), - function_definition_type(), - module_type(), - type_of_quoted_types(), - top_level_item_type(), - quoted_type(), - typed_expr_type(), - comptime_string_type(), - )) -} + if let Some(path) = self.parse_path_no_turbofish() { + let generics = self.parse_generic_type_args(); + return Some(UnresolvedTypeData::Named(path, generics, false)); + } -/// This is the type `Expr` - the type of a quoted, untyped expression object used for macros -pub(super) fn expr_type() -> impl NoirParser { - keyword(Keyword::Expr) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Expr).with_span(span)) -} + None + } -/// This is the type `StructDefinition` - the type of a quoted struct definition -pub(super) fn struct_definition_type() -> impl NoirParser { - keyword(Keyword::StructDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::StructDefinition).with_span(span) - }) -} + pub(super) fn parse_primitive_type(&mut self) -> Option { + if let Some(typ) = self.parse_field_type() { + return Some(typ); + } -/// This is the type `TraitConstraint` - the type of a quoted trait constraint -pub(super) fn trait_constraint_type() -> impl NoirParser { - keyword(Keyword::TraitConstraint).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TraitConstraint).with_span(span) - }) -} + if let Some(typ) = self.parse_int_type() { + return Some(typ); + } -pub(super) fn trait_definition_type() -> impl NoirParser { - keyword(Keyword::TraitDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TraitDefinition).with_span(span) - }) -} + if let Some(typ) = self.parse_bool_type() { + return Some(typ); + } -pub(super) fn trait_impl_type() -> impl NoirParser { - keyword(Keyword::TraitImpl) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TraitImpl).with_span(span)) -} + if let Some(typ) = self.parse_str_type() { + return Some(typ); + } -pub(super) fn unresolved_type_type() -> impl NoirParser { - keyword(Keyword::UnresolvedType).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::UnresolvedType).with_span(span) - }) -} + if let Some(typ) = self.parse_fmtstr_type() { + return Some(typ); + } -pub(super) fn function_definition_type() -> impl NoirParser { - keyword(Keyword::FunctionDefinition).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::FunctionDefinition).with_span(span) - }) -} + if let Some(typ) = self.parse_comptime_type() { + return Some(typ); + } -pub(super) fn module_type() -> impl NoirParser { - keyword(Keyword::Module) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Module).with_span(span)) -} + if let Some(typ) = self.parse_resolved_type() { + return Some(typ); + } -/// This is the type `TopLevelItem` - the type of a quoted statement in the top level. -/// E.g. a type definition, trait definition, trait impl, function, etc. -fn top_level_item_type() -> impl NoirParser { - keyword(Keyword::TopLevelItem).map_with_span(|_, span| { - UnresolvedTypeData::Quoted(QuotedType::TopLevelItem).with_span(span) - }) -} + if let Some(typ) = self.parse_interned_type() { + return Some(typ); + } -/// This is the type `Type` - the type of a quoted noir type. -fn type_of_quoted_types() -> impl NoirParser { - keyword(Keyword::TypeType) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Type).with_span(span)) -} + None + } -/// This is the type of a quoted, unparsed token stream. -fn quoted_type() -> impl NoirParser { - keyword(Keyword::Quoted) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::Quoted).with_span(span)) -} + fn parse_bool_type(&mut self) -> Option { + if self.eat_keyword(Keyword::Bool) { + return Some(UnresolvedTypeData::Bool); + } -/// This is the type of a typed/resolved expression. -fn typed_expr_type() -> impl NoirParser { - keyword(Keyword::TypedExpr) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::TypedExpr).with_span(span)) -} + None + } -/// This is the `CtString` type for dynamically-sized compile-time strings -fn comptime_string_type() -> impl NoirParser { - keyword(Keyword::CtString) - .map_with_span(|_, span| UnresolvedTypeData::Quoted(QuotedType::CtString).with_span(span)) -} + fn parse_field_type(&mut self) -> Option { + if self.eat_keyword(Keyword::Field) { + return Some(UnresolvedTypeData::FieldElement); + } -/// This is the type of an already resolved type. -/// The only way this can appear in the token input is if an already resolved `Type` object -/// was spliced into a macro's token stream via the `$` operator. -pub(super) fn resolved_type() -> impl NoirParser { - token_kind(TokenKind::QuotedType).map_with_span(|token, span| match token { - Token::QuotedType(id) => UnresolvedTypeData::Resolved(id).with_span(span), - _ => unreachable!("token_kind(QuotedType) guarantees we parse a quoted type"), - }) -} + None + } -pub(super) fn interned_unresolved_type() -> impl NoirParser { - token_kind(TokenKind::InternedUnresolvedTypeData).map_with_span(|token, span| match token { - Token::InternedUnresolvedTypeData(id) => UnresolvedTypeData::Interned(id).with_span(span), - _ => unreachable!( - "token_kind(InternedUnresolvedTypeData) guarantees we parse an interned unresolved type" - ), - }) -} + fn parse_int_type(&mut self) -> Option { + if let Some(int_type) = self.eat_int_type() { + return Some(match UnresolvedTypeData::from_int_token(int_type) { + Ok(typ) => typ, + Err(err) => { + self.push_error( + ParserErrorReason::InvalidBitSize(err.0), + self.previous_token_span, + ); + UnresolvedTypeData::Error + } + }); + } -pub(super) fn string_type() -> impl NoirParser { - keyword(Keyword::String) - .ignore_then(type_expression().delimited_by(just(Token::Less), just(Token::Greater))) - .map_with_span(|expr, span| UnresolvedTypeData::String(expr).with_span(span)) -} + None + } -pub(super) fn format_string_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - keyword(Keyword::FormatString) - .ignore_then( - type_expression() - .then_ignore(just(Token::Comma)) - .then(type_parser) - .delimited_by(just(Token::Less), just(Token::Greater)), - ) - .map_with_span(|(size, fields), span| { - UnresolvedTypeData::FormatString(size, Box::new(fields)).with_span(span) - }) -} + fn parse_str_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::String) { + return None; + } -pub(super) fn int_type() -> impl NoirParser { - filter_map(|span, token: Token| match token { - Token::IntType(int_type) => Ok(int_type), - unexpected => { - Err(ParserError::expected_label(ParsingRuleLabel::IntegerType, unexpected, span)) - } - }) - .validate(|token, span, emit| { - UnresolvedTypeData::from_int_token(token).map(|data| data.with_span(span)).unwrap_or_else( - |err| { - emit(ParserError::with_reason(ParserErrorReason::InvalidBitSize(err.0), span)); - UnresolvedType::error(span) - }, - ) - }) -} + if !self.eat_less() { + self.expected_token(Token::Less); + let expr = UnresolvedTypeExpression::Constant(0, self.current_token_span); + return Some(UnresolvedTypeData::String(expr)); + } -pub(super) fn named_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - path_no_turbofish().then(generic_type_args(type_parser)).map_with_span(|(path, args), span| { - UnresolvedTypeData::Named(path, args, false).with_span(span) - }) -} + let expr = match self.parse_type_expression() { + Ok(expr) => expr, + Err(error) => { + self.errors.push(error); + UnresolvedTypeExpression::Constant(0, self.current_token_span) + } + }; -pub(super) fn named_trait<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - keyword(Keyword::Impl) - .ignore_then(path_no_turbofish()) - .then(generic_type_args(type_parser)) - .map_with_span(|(path, args), span| { - UnresolvedTypeData::TraitAsType(path, args).with_span(span) - }) -} + self.eat_or_error(Token::Greater); -pub(super) fn generic_type_args<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - required_generic_type_args(type_parser).or_not().map(Option::unwrap_or_default) -} + Some(UnresolvedTypeData::String(expr)) + } -pub(super) fn required_generic_type_args<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - let generic_type_arg = type_parser - .clone() - .then_ignore(one_of([Token::Comma, Token::Greater]).rewind()) - .or(type_expression_validated()); - - let named_arg = ident() - .then_ignore(just(Token::Assign)) - .then(generic_type_arg.clone()) - .map(|(name, typ)| GenericTypeArg::Named(name, typ)); - - // We need to parse named arguments first since otherwise when we see - // `Foo = Bar`, just `Foo` is a valid type, and we'd parse an ordered - // generic before erroring that an `=` is invalid after an ordered generic. - choice((named_arg, generic_type_arg.map(GenericTypeArg::Ordered))) - .boxed() - // Without checking for a terminating ',' or '>' here we may incorrectly - // parse a generic `N * 2` as just the type `N` then fail when there is no - // separator afterward. Failing early here ensures we try the `type_expression` - // parser afterward. - .separated_by(just(Token::Comma)) - .allow_trailing() - .at_least(1) - .delimited_by(just(Token::Less), just(Token::Greater)) - .map(GenericTypeArgs::from) -} + fn parse_fmtstr_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::FormatString) { + return None; + } -pub(super) fn array_type<'a>( - type_parser: impl NoirParser + 'a, -) -> impl NoirParser + 'a { - just(Token::LeftBracket) - .ignore_then(type_parser) - .then(just(Token::Semicolon).ignore_then(type_expression())) - .then_ignore(just(Token::RightBracket)) - .map_with_span(|(element_type, size), span| { - UnresolvedTypeData::Array(size, Box::new(element_type)).with_span(span) - }) -} + if !self.eat_less() { + self.expected_token(Token::Less); + let expr = UnresolvedTypeExpression::Constant(0, self.current_token_span); + let typ = UnresolvedTypeData::Error.with_span(self.span_at_previous_token_end()); + return Some(UnresolvedTypeData::FormatString(expr, Box::new(typ))); + } -pub(super) fn slice_type( - type_parser: impl NoirParser, -) -> impl NoirParser { - just(Token::LeftBracket) - .ignore_then(type_parser) - .then_ignore(just(Token::RightBracket)) - .map_with_span(|element_type, span| { - UnresolvedTypeData::Slice(Box::new(element_type)).with_span(span) - }) -} + let expr = match self.parse_type_expression() { + Ok(expr) => expr, + Err(error) => { + self.errors.push(error); + UnresolvedTypeExpression::Constant(0, self.current_token_span) + } + }; -fn type_expression() -> impl NoirParser { - type_expression_inner().try_map(UnresolvedTypeExpression::from_expr) -} + if !self.eat_commas() { + self.expected_token(Token::Comma); + } + + let typ = self.parse_type_or_error(); + + self.eat_or_error(Token::Greater); + + Some(UnresolvedTypeData::FormatString(expr, Box::new(typ))) + } + + fn parse_comptime_type(&mut self) -> Option { + if self.eat_keyword(Keyword::Expr) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Expr)); + } + if self.eat_keyword(Keyword::Quoted) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Quoted)); + } + if self.eat_keyword(Keyword::TopLevelItem) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TopLevelItem)); + } + if self.eat_keyword(Keyword::TypeType) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Type)); + } + if self.eat_keyword(Keyword::TypedExpr) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TypedExpr)); + } + if self.eat_keyword(Keyword::StructDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::StructDefinition)); + } + if self.eat_keyword(Keyword::TraitConstraint) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TraitConstraint)); + } + if self.eat_keyword(Keyword::TraitDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TraitDefinition)); + } + if self.eat_keyword(Keyword::TraitImpl) { + return Some(UnresolvedTypeData::Quoted(QuotedType::TraitImpl)); + } + if self.eat_keyword(Keyword::UnresolvedType) { + return Some(UnresolvedTypeData::Quoted(QuotedType::UnresolvedType)); + } + if self.eat_keyword(Keyword::FunctionDefinition) { + return Some(UnresolvedTypeData::Quoted(QuotedType::FunctionDefinition)); + } + if self.eat_keyword(Keyword::Module) { + return Some(UnresolvedTypeData::Quoted(QuotedType::Module)); + } + if self.eat_keyword(Keyword::CtString) { + return Some(UnresolvedTypeData::Quoted(QuotedType::CtString)); + } + None + } -/// This parser is the same as `type_expression()`, however, it continues parsing and -/// emits a parser error in the case of an invalid type expression rather than halting the parser. -pub(super) fn type_expression_validated() -> impl NoirParser { - type_expression_inner().validate(|expr, span, emit| { - let type_expr = UnresolvedTypeExpression::from_expr(expr, span); - match type_expr { - Ok(type_expression) => UnresolvedTypeData::Expression(type_expression).with_span(span), - Err(parser_error) => { - emit(parser_error); - UnresolvedType::error(span) + fn parse_function_type(&mut self) -> Option { + let unconstrained = self.eat_keyword(Keyword::Unconstrained); + + if !self.eat_keyword(Keyword::Fn) { + if unconstrained { + self.expected_token(Token::Keyword(Keyword::Fn)); + return Some(UnresolvedTypeData::Function( + Vec::new(), + Box::new(self.unspecified_type_at_previous_token_end()), + Box::new(self.unspecified_type_at_previous_token_end()), + unconstrained, + )); } + + return None; } - }) -} -fn type_expression_inner() -> impl NoirParser { - recursive(|expr| { - expression_with_precedence( - Precedence::lowest_type_precedence(), - expr, - nothing(), - nothing(), - true, - false, - ) - }) - .labelled(ParsingRuleLabel::TypeExpression) -} + let env = if self.eat_left_bracket() { + let typ = self.parse_type_or_error(); + self.eat_or_error(Token::RightBracket); + typ + } else { + UnresolvedTypeData::Unit.with_span(self.span_at_previous_token_end()) + }; + + if !self.eat_left_paren() { + self.expected_token(Token::LeftParen); + + return Some(UnresolvedTypeData::Function( + Vec::new(), + Box::new(self.unspecified_type_at_previous_token_end()), + Box::new(self.unspecified_type_at_previous_token_end()), + unconstrained, + )); + } + + let args = self.parse_many( + "parameters", + separated_by_comma_until_right_paren(), + Self::parse_parameter, + ); + + let ret = if self.eat(Token::Arrow) { + self.parse_type_or_error() + } else { + self.expected_token(Token::Arrow); + UnresolvedTypeData::Unit.with_span(self.span_at_previous_token_end()) + }; + + Some(UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env), unconstrained)) + } -pub(super) fn tuple_type(type_parser: T) -> impl NoirParser -where - T: NoirParser, -{ - let fields = type_parser.separated_by(just(Token::Comma)).allow_trailing(); - parenthesized(fields).map_with_span(|fields, span| { - if fields.is_empty() { - UnresolvedTypeData::Unit.with_span(span) + fn parse_parameter(&mut self) -> Option { + let typ = self.parse_type_or_error(); + if let UnresolvedTypeData::Error = typ.typ { + None } else { - UnresolvedTypeData::Tuple(fields).with_span(span) + Some(typ) } - }) -} + } -pub(super) fn function_type(type_parser: T) -> impl NoirParser -where - T: NoirParser, -{ - let args = parenthesized(type_parser.clone().separated_by(just(Token::Comma)).allow_trailing()); - - let env = just(Token::LeftBracket) - .ignore_then(type_parser.clone()) - .then_ignore(just(Token::RightBracket)) - .or_not() - .map_with_span(|t, span| { - t.unwrap_or_else(|| UnresolvedTypeData::Unit.with_span(Span::empty(span.end()))) - }); - - keyword(Keyword::Unconstrained) - .or_not() - .then(keyword(Keyword::Fn)) - .map(|(unconstrained_token, _fn_token)| unconstrained_token.is_some()) - .then(env) - .then(args) - .then_ignore(just(Token::Arrow)) - .then(type_parser) - .map_with_span(|(((unconstrained, env), args), ret), span| { - UnresolvedTypeData::Function(args, Box::new(ret), Box::new(env), unconstrained) - .with_span(span) - }) -} + fn parse_trait_as_type(&mut self) -> Option { + if !self.eat_keyword(Keyword::Impl) { + return None; + } + + let Some(path) = self.parse_path_no_turbofish() else { + self.expected_label(ParsingRuleLabel::Path); + return None; + }; + + let generics = self.parse_generic_type_args(); -pub(super) fn mutable_reference_type(type_parser: T) -> impl NoirParser -where - T: NoirParser, -{ - just(Token::Ampersand) - .ignore_then(keyword(Keyword::Mut)) - .ignore_then(type_parser) - .map_with_span(|element, span| { - UnresolvedTypeData::MutableReference(Box::new(element)).with_span(span) + Some(UnresolvedTypeData::TraitAsType(path, generics)) + } + + fn parse_as_trait_path_type(&mut self) -> Option { + let as_trait_path = self.parse_as_trait_path()?; + Some(UnresolvedTypeData::AsTraitPath(Box::new(as_trait_path))) + } + + fn parse_resolved_type(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::QuotedType) { + match token.into_token() { + Token::QuotedType(id) => { + return Some(UnresolvedTypeData::Resolved(id)); + } + _ => unreachable!(), + } + } + + None + } + + pub(super) fn parse_interned_type(&mut self) -> Option { + if let Some(token) = self.eat_kind(TokenKind::InternedUnresolvedTypeData) { + match token.into_token() { + Token::InternedUnresolvedTypeData(id) => { + return Some(UnresolvedTypeData::Interned(id)); + } + _ => unreachable!(), + } + } + + None + } + + fn parses_mutable_reference_type(&mut self) -> Option { + if self.eat(Token::Ampersand) { + self.eat_keyword_or_error(Keyword::Mut); + return Some(UnresolvedTypeData::MutableReference(Box::new( + self.parse_type_or_error(), + ))); + }; + + None + } + + fn parse_array_or_slice_type(&mut self) -> Option { + if !self.eat_left_bracket() { + return None; + } + + let typ = self.parse_type_or_error(); + + if self.eat_semicolon() { + match self.parse_type_expression() { + Ok(expr) => { + self.eat_or_error(Token::RightBracket); + Some(UnresolvedTypeData::Array(expr, Box::new(typ))) + } + Err(error) => { + self.errors.push(error); + self.eat_or_error(Token::RightBracket); + Some(UnresolvedTypeData::Slice(Box::new(typ))) + } + } + } else { + self.eat_or_error(Token::RightBracket); + Some(UnresolvedTypeData::Slice(Box::new(typ))) + } + } + + fn parse_parentheses_type(&mut self) -> Option { + if !self.eat_left_paren() { + return None; + } + + if self.eat_right_paren() { + return Some(UnresolvedTypeData::Unit); + } + + let (mut types, trailing_comma) = self.parse_many_return_trailing_separator_if_any( + "tuple elements", + separated_by_comma_until_right_paren(), + Self::parse_type_in_list, + ); + + Some(if types.len() == 1 && !trailing_comma { + UnresolvedTypeData::Parenthesized(Box::new(types.remove(0))) + } else { + UnresolvedTypeData::Tuple(types) }) + } + + pub(super) fn parse_type_in_list(&mut self) -> Option { + if let Some(typ) = self.parse_type() { + Some(typ) + } else { + self.expected_label(ParsingRuleLabel::Type); + None + } + } + + /// OptionalTypeAnnotation = ( ':' Type )? + pub(super) fn parse_optional_type_annotation(&mut self) -> UnresolvedType { + if self.eat_colon() { + self.parse_type_or_error() + } else { + self.unspecified_type_at_previous_token_end() + } + } + + pub(super) fn unspecified_type_at_previous_token_end(&self) -> UnresolvedType { + UnresolvedTypeData::Unspecified.with_span(self.span_at_previous_token_end()) + } } #[cfg(test)] -mod test { - use super::*; - use crate::parser::parser::test_helpers::*; +mod tests { + use strum::IntoEnumIterator; + + use crate::{ + ast::{IntegerBitSize, Signedness, UnresolvedType, UnresolvedTypeData}, + parser::{ + parser::tests::{expect_no_errors, get_single_error, get_source_with_error_span}, + Parser, + }, + QuotedType, + }; + + fn parse_type_no_errors(src: &str) -> UnresolvedType { + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_error(); + expect_no_errors(&parser.errors); + typ + } + + #[test] + fn parses_unit_type() { + let src = "()"; + let typ = parse_type_no_errors(src); + assert!(matches!(typ.typ, UnresolvedTypeData::Unit)); + } + + #[test] + fn parses_bool_type() { + let src = "bool"; + let typ = parse_type_no_errors(src); + assert!(matches!(typ.typ, UnresolvedTypeData::Bool)); + } + + #[test] + fn parses_int_type() { + let src = "u32"; + let typ = parse_type_no_errors(src); + assert!(matches!( + typ.typ, + UnresolvedTypeData::Integer(Signedness::Unsigned, IntegerBitSize::ThirtyTwo) + )); + } + + #[test] + fn parses_field_type() { + let src = "Field"; + let typ = parse_type_no_errors(src); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_str_type() { + let src = "str<10>"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::String(expr) = typ.typ else { panic!("Expected a string type") }; + assert_eq!(expr.to_string(), "10"); + } + + #[test] + fn parses_fmtstr_type() { + let src = "fmtstr<10, T>"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::FormatString(expr, typ) = typ.typ else { + panic!("Expected a format string type") + }; + assert_eq!(expr.to_string(), "10"); + assert_eq!(typ.to_string(), "T"); + } + + #[test] + fn parses_comptime_types() { + for quoted_type in QuotedType::iter() { + let src = quoted_type.to_string(); + let typ = parse_type_no_errors(&src); + let UnresolvedTypeData::Quoted(parsed_qouted_type) = typ.typ else { + panic!("Expected a quoted type for {}", quoted_type) + }; + assert_eq!(parsed_qouted_type, quoted_type); + } + } + + #[test] + fn parses_tuple_type() { + let src = "(Field, bool)"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Tuple(mut types) = typ.typ else { panic!("Expected a tuple type") }; + assert_eq!(types.len(), 2); + + let typ = types.remove(0); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + + let typ = types.remove(0); + assert!(matches!(typ.typ, UnresolvedTypeData::Bool)); + } + + #[test] + fn parses_tuple_type_one_element() { + let src = "(Field,)"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Tuple(mut types) = typ.typ else { panic!("Expected a tuple type") }; + assert_eq!(types.len(), 1); + + let typ = types.remove(0); + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_parenthesized_type() { + let src = "(Field)"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Parenthesized(typ) = typ.typ else { + panic!("Expected a parenthesized type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_unclosed_parentheses_type() { + let src = "(Field"; + let mut parser = Parser::for_str(src); + let typ = parser.parse_type_or_error(); + assert_eq!(parser.errors.len(), 1); + let UnresolvedTypeData::Parenthesized(typ) = typ.typ else { + panic!("Expected a parenthesized type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_mutable_reference_type() { + let src = "&mut Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::MutableReference(typ) = typ.typ else { + panic!("Expected a mutable reference type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn parses_named_type_no_generics() { + let src = "foo::Bar"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Named(path, generics, _) = typ.typ else { + panic!("Expected a named type") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(generics.is_empty()); + } + + #[test] + fn parses_slice_type() { + let src = "[Field]"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Slice(typ) = typ.typ else { panic!("Expected a slice type") }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + } + + #[test] + fn errors_if_missing_right_bracket_after_slice_type() { + let src = " + [Field + ^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + parser.parse_type(); + let error = get_single_error(&parser.errors, span); + assert_eq!(error.to_string(), "Expected a ] but found end of input"); + } + + #[test] + fn parses_array_type() { + let src = "[Field; 10]"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Array(expr, typ) = typ.typ else { + panic!("Expected an array type") + }; + assert!(matches!(typ.typ, UnresolvedTypeData::FieldElement)); + assert_eq!(expr.to_string(), "10"); + } + + #[test] + fn parses_empty_function_type() { + let src = "fn() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(args, ret, env, unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert!(args.is_empty()); + assert_eq!(ret.typ.to_string(), "Field"); + assert!(matches!(env.typ, UnresolvedTypeData::Unit)); + assert!(!unconstrained); + } + + #[test] + fn parses_function_type_with_arguments() { + let src = "fn(Field, bool) -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(args, _ret, _env, _unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert_eq!(args.len(), 2); + assert_eq!(args[0].typ.to_string(), "Field"); + assert_eq!(args[1].typ.to_string(), "bool"); + } + + #[test] + fn parses_function_type_with_return_type() { + let src = "fn() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(_args, ret, _env, _unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert_eq!(ret.typ.to_string(), "Field"); + } + + #[test] + fn parses_function_type_with_env() { + let src = "fn[Field]() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(_args, _ret, env, _unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert_eq!(env.typ.to_string(), "Field"); + } + + #[test] + fn parses_unconstrained_function_type() { + let src = "unconstrained fn() -> Field"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::Function(_args, _ret, _env, unconstrained) = typ.typ else { + panic!("Expected a function type") + }; + assert!(unconstrained); + } + + #[test] + fn parses_trait_as_type_no_generics() { + let src = "impl foo::Bar"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::TraitAsType(path, generics) = typ.typ else { + panic!("Expected trait as type") + }; + assert_eq!(path.to_string(), "foo::Bar"); + assert!(generics.is_empty()); + } #[test] - fn parse_type_expression() { - parse_all(type_expression(), vec!["(123)", "123", "(1 + 1)", "(1 + (1))"]); + fn parses_as_trait_path() { + let src = "::baz"; + let typ = parse_type_no_errors(src); + let UnresolvedTypeData::AsTraitPath(as_trait_path) = typ.typ else { + panic!("Expected as_trait_path") + }; + assert_eq!(as_trait_path.typ.typ.to_string(), "Field"); + assert_eq!(as_trait_path.trait_path.to_string(), "foo::Bar"); + assert!(as_trait_path.trait_generics.is_empty()); + assert_eq!(as_trait_path.impl_item.to_string(), "baz"); } } diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/use_tree.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/use_tree.rs new file mode 100644 index 00000000000..1c43732c94f --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/use_tree.rs @@ -0,0 +1,253 @@ +use noirc_errors::Span; + +use crate::{ + ast::{Ident, Path, PathKind, UseTree, UseTreeKind}, + parser::labels::ParsingRuleLabel, + token::{Keyword, Token}, +}; + +use super::{parse_many::separated_by_comma_until_right_brace, Parser}; + +impl<'a> Parser<'a> { + /// Use = 'use' PathKind PathNoTurbofish UseTree + /// + /// UseTree = PathNoTurbofish ( '::' '{' UseTreeList? '}' )? + /// + /// UseTreeList = UseTree (',' UseTree)* ','? + pub(super) fn parse_use_tree(&mut self) -> UseTree { + let start_span = self.current_token_span; + + let kind = self.parse_path_kind(); + + let use_tree = self.parse_use_tree_without_kind( + start_span, kind, false, // nested + ); + if !self.eat_semicolons() { + self.expected_token(Token::Semicolon); + } + use_tree + } + + pub(super) fn parse_use_tree_without_kind( + &mut self, + start_span: Span, + kind: PathKind, + nested: bool, + ) -> UseTree { + let prefix = self.parse_path_after_kind( + kind, false, // allow turbofish + false, // allow trailing double colon + start_span, + ); + let trailing_double_colon = if prefix.segments.is_empty() && kind != PathKind::Plain { + true + } else { + self.eat_double_colon() + }; + + if trailing_double_colon { + if self.eat_left_brace() { + let use_trees = self.parse_many( + "use trees", + separated_by_comma_until_right_brace(), + Self::parse_use_tree_in_list, + ); + + UseTree { prefix, kind: UseTreeKind::List(use_trees) } + } else { + self.expected_token(Token::LeftBrace); + self.parse_path_use_tree_end(prefix, nested) + } + } else { + self.parse_path_use_tree_end(prefix, nested) + } + } + + fn parse_use_tree_in_list(&mut self) -> Option { + let start_span = self.current_token_span; + + let use_tree = self.parse_use_tree_without_kind( + start_span, + PathKind::Plain, + true, // nested + ); + + // If we didn't advance at all, we are done + if start_span == self.current_token_span { + self.expected_label(ParsingRuleLabel::UseSegment); + None + } else { + Some(use_tree) + } + } + + pub(super) fn parse_path_use_tree_end(&mut self, mut prefix: Path, nested: bool) -> UseTree { + if prefix.segments.is_empty() { + if nested { + self.expected_identifier(); + } else { + self.expected_label(ParsingRuleLabel::UseSegment); + } + UseTree { prefix, kind: UseTreeKind::Path(Ident::default(), None) } + } else { + let ident = prefix.segments.pop().unwrap().ident; + if self.eat_keyword(Keyword::As) { + if let Some(alias) = self.eat_ident() { + UseTree { prefix, kind: UseTreeKind::Path(ident, Some(alias)) } + } else { + self.expected_identifier(); + UseTree { prefix, kind: UseTreeKind::Path(ident, None) } + } + } else { + UseTree { prefix, kind: UseTreeKind::Path(ident, None) } + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::{ItemVisibility, PathKind, UseTree, UseTreeKind}, + parser::{ + parser::{parse_program, tests::expect_no_errors}, + ItemKind, + }, + }; + + fn parse_use_tree_no_errors(src: &str) -> (UseTree, ItemVisibility) { + let (mut module, errors) = parse_program(src); + expect_no_errors(&errors); + assert_eq!(module.items.len(), 1); + let item = module.items.remove(0); + let ItemKind::Import(use_tree, visibility) = item.kind else { + panic!("Expected import"); + }; + (use_tree, visibility) + } + + #[test] + fn parse_simple() { + let src = "use foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = &use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_simple_pub() { + let src = "pub use foo;"; + let (_use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Public); + } + + #[test] + fn parse_simple_pub_crate() { + let src = "pub(crate) use foo;"; + let (_use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::PublicCrate); + } + + #[test] + fn parse_simple_with_alias() { + let src = "use foo as bar;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo as bar", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert_eq!("bar", alias.unwrap().to_string()); + } + + #[test] + fn parse_with_crate_prefix() { + let src = "use crate::foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Crate); + assert_eq!("crate::foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_with_dep_prefix() { + let src = "use dep::foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Dep); + assert_eq!("dep::foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_with_super_prefix() { + let src = "use super::foo;"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Super); + assert_eq!("super::foo", use_tree.to_string()); + let UseTreeKind::Path(ident, alias) = use_tree.kind else { + panic!("Expected path"); + }; + assert_eq!("foo", ident.to_string()); + assert!(alias.is_none()); + } + + #[test] + fn parse_list() { + let src = "use foo::{bar, baz};"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo::{bar, baz}", use_tree.to_string()); + let UseTreeKind::List(use_trees) = &use_tree.kind else { + panic!("Expected list"); + }; + assert_eq!(use_trees.len(), 2); + } + + #[test] + fn parse_list_trailing_comma() { + let src = "use foo::{bar, baz, };"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!(use_tree.prefix.kind, PathKind::Plain); + assert_eq!("foo::{bar, baz}", use_tree.to_string()); + let UseTreeKind::List(use_trees) = &use_tree.kind else { + panic!("Expected list"); + }; + assert_eq!(use_trees.len(), 2); + } + + #[test] + fn parse_list_that_starts_with_crate() { + let src = "use crate::{foo, bar};"; + let (use_tree, visibility) = parse_use_tree_no_errors(src); + assert_eq!(visibility, ItemVisibility::Private); + assert_eq!("crate::{foo, bar}", use_tree.to_string()); + } + + #[test] + fn errors_on_crate_in_subtree() { + let src = "use foo::{crate::bar}"; + let (_, errors) = parse_program(src); + assert!(!errors.is_empty()); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs deleted file mode 100644 index ea46becc932..00000000000 --- a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/visibility.rs +++ /dev/null @@ -1,36 +0,0 @@ -use chumsky::{ - prelude::{choice, empty, just}, - Parser, -}; - -use crate::{ - ast::{ItemVisibility, Visibility}, - parser::NoirParser, - token::{Keyword, Token}, -}; - -use super::{call_data, primitives::keyword}; - -/// visibility_modifier: 'pub(crate)'? 'pub'? '' -pub(crate) fn item_visibility() -> impl NoirParser { - let is_pub_crate = (keyword(Keyword::Pub) - .then_ignore(just(Token::LeftParen)) - .then_ignore(keyword(Keyword::Crate)) - .then_ignore(just(Token::RightParen))) - .map(|_| ItemVisibility::PublicCrate); - - let is_pub = keyword(Keyword::Pub).map(|_| ItemVisibility::Public); - - let is_private = empty().map(|_| ItemVisibility::Private); - - choice((is_pub_crate, is_pub, is_private)) -} - -pub fn visibility() -> impl NoirParser { - keyword(Keyword::Pub) - .map(|_| Visibility::Public) - .or(call_data()) - .or(keyword(Keyword::ReturnData).map(|_| Visibility::ReturnData)) - .or_not() - .map(|opt| opt.unwrap_or(Visibility::Private)) -} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs new file mode 100644 index 00000000000..a753ffb6fd2 --- /dev/null +++ b/noir/noir-repo/compiler/noirc_frontend/src/parser/parser/where_clause.rs @@ -0,0 +1,188 @@ +use crate::{ + ast::{GenericTypeArgs, Path, PathKind, TraitBound, UnresolvedTraitConstraint, UnresolvedType}, + parser::labels::ParsingRuleLabel, + token::{Keyword, Token}, +}; + +use super::{ + parse_many::{separated_by, separated_by_comma}, + Parser, +}; + +impl<'a> Parser<'a> { + /// WhereClause = 'where' WhereClauseItems? + /// + /// WhereClauseItems = WhereClauseItem ( ',' WhereClauseItem )* ','? + /// + /// WhereClauseItem = Type ':' TraitBounds + pub(super) fn parse_where_clause(&mut self) -> Vec { + if !self.eat_keyword(Keyword::Where) { + return Vec::new(); + } + + // Constraints might end up being empty, but that's accepted as valid syntax + let constraints = + self.parse_many("where clauses", separated_by_comma(), Self::parse_single_where_clause); + + constraints + .into_iter() + .flat_map(|(typ, trait_bounds)| { + trait_bounds.into_iter().map(move |trait_bound| UnresolvedTraitConstraint { + typ: typ.clone(), + trait_bound, + }) + }) + .collect() + } + + fn parse_single_where_clause(&mut self) -> Option<(UnresolvedType, Vec)> { + let Some(typ) = self.parse_type() else { + return None; + }; + + self.eat_or_error(Token::Colon); + + let trait_bounds = self.parse_trait_bounds(); + + Some((typ, trait_bounds)) + } + + /// TraitBounds = TraitBound ( '+' TraitBound )? '+'? + pub(super) fn parse_trait_bounds(&mut self) -> Vec { + self.parse_many( + "trait bounds", + separated_by(Token::Plus).stop_if_separator_is_missing(), + Self::parse_trait_bound_in_list, + ) + } + + fn parse_trait_bound_in_list(&mut self) -> Option { + if let Some(trait_bound) = self.parse_trait_bound() { + Some(trait_bound) + } else { + self.expected_label(ParsingRuleLabel::TraitBound); + None + } + } + + pub(crate) fn parse_trait_bound_or_error(&mut self) -> TraitBound { + if let Some(trait_bound) = self.parse_trait_bound() { + return trait_bound; + } + + self.expected_label(ParsingRuleLabel::TraitBound); + TraitBound { + trait_path: Path { + kind: PathKind::Plain, + segments: Vec::new(), + span: self.span_at_previous_token_end(), + }, + trait_id: None, + trait_generics: GenericTypeArgs::default(), + } + } + + /// TraitBound = PathNoTurbofish GenericTypeArgs + pub(crate) fn parse_trait_bound(&mut self) -> Option { + let trait_path = self.parse_path_no_turbofish()?; + let trait_generics = self.parse_generic_type_args(); + Some(TraitBound { trait_path, trait_generics, trait_id: None }) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + ast::UnresolvedTraitConstraint, + parser::{ + parser::tests::{ + expect_no_errors, get_single_error_reason, get_source_with_error_span, + }, + Parser, ParserErrorReason, + }, + token::Token, + }; + + fn parse_where_clause_no_errors(src: &str) -> Vec { + let mut parser = Parser::for_str(src); + let constraints = parser.parse_where_clause(); + expect_no_errors(&parser.errors); + constraints + } + + #[test] + fn parses_no_where_clause() { + let src = "{"; + let constraints = parse_where_clause_no_errors(src); + assert!(constraints.is_empty()); + } + + #[test] + fn parses_one_where_clause_with_two_constraints() { + let src = "where Foo: Bar + Baz"; + let mut constraints = parse_where_clause_no_errors(src); + assert_eq!(constraints.len(), 2); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Bar"); + assert_eq!(constraint.trait_bound.trait_generics.ordered_args[0].to_string(), "T"); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Baz"); + } + + #[test] + fn parses_two_where_clauses() { + let src = "where Foo: Bar, i32: Qux {"; + let mut constraints = parse_where_clause_no_errors(src); + assert_eq!(constraints.len(), 2); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Bar"); + assert_eq!(constraint.trait_bound.trait_generics.ordered_args[0].to_string(), "T"); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "i32"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Qux"); + } + + #[test] + fn parses_two_where_clauses_missing_comma() { + let src = " + where Foo: Bar i32: Qux { + ^^^ + "; + let (src, span) = get_source_with_error_span(src); + let mut parser = Parser::for_str(&src); + let mut constraints = parser.parse_where_clause(); + + let reason = get_single_error_reason(&parser.errors, span); + let ParserErrorReason::ExpectedTokenSeparatingTwoItems { token, items } = reason else { + panic!("Expected a different error"); + }; + assert_eq!(token, &Token::Comma); + assert_eq!(*items, "where clauses"); + + assert_eq!(constraints.len(), 2); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "Foo"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Bar"); + assert_eq!(constraint.trait_bound.trait_generics.ordered_args[0].to_string(), "T"); + + let constraint = constraints.remove(0); + assert_eq!(constraint.typ.to_string(), "i32"); + assert_eq!(constraint.trait_bound.trait_path.to_string(), "Qux"); + } + + #[test] + fn parses_where_clause_missing_trait_bound() { + let src = "where Foo: "; + let mut parser = Parser::for_str(src); + parser.parse_where_clause(); + assert!(!parser.errors.is_empty()); + } +} diff --git a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs index a8d8c807797..65841b643c4 100644 --- a/noir/noir-repo/compiler/noirc_frontend/src/tests.rs +++ b/noir/noir-repo/compiler/noirc_frontend/src/tests.rs @@ -30,16 +30,13 @@ use crate::hir::Context; use crate::node_interner::{NodeInterner, StmtId}; use crate::hir::def_collector::dc_crate::DefCollector; +use crate::hir::def_map::{CrateDefMap, LocalModuleId}; use crate::hir_def::expr::HirExpression; use crate::hir_def::stmt::HirStatement; use crate::monomorphization::monomorphize; use crate::parser::{ItemKind, ParserErrorReason}; use crate::token::SecondaryAttribute; -use crate::ParsedModule; -use crate::{ - hir::def_map::{CrateDefMap, LocalModuleId}, - parse_program, -}; +use crate::{parse_program, ParsedModule}; use fm::FileManager; use noirc_arena::Arena; diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md index 5a8b43b1dfa..71a36e629c6 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/typ.md @@ -63,6 +63,12 @@ return the numeric constant. If this is an integer type, return a boolean which is `true` if the type is signed, as well as the number of bits of this integer type. +### as_mutable_reference + +#include_code as_mutable_reference noir_stdlib/src/meta/typ.nr rust + +If this is a mutable reference type `&mut T`, returns the mutable type `T`. + ### as_slice #include_code as_slice noir_stdlib/src/meta/typ.nr rust @@ -146,6 +152,12 @@ fn foo() where T: Default { `true` if this type is `Field`. +### is_unit + +#include_code is_unit noir_stdlib/src/meta/typ.nr rust + +`true` if this type is the unit `()` type. + ## Trait Implementations ```rust diff --git a/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md b/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md index 9c61f91dee2..8535cbab19c 100644 --- a/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md +++ b/noir/noir-repo/docs/docs/noir/standard_library/meta/unresolved_type.md @@ -6,8 +6,32 @@ title: UnresolvedType ## Methods +### as_mutable_reference + +#include_code as_mutable_reference noir_stdlib/src/meta/unresolved_type.nr rust + +If this is a mutable reference type `&mut T`, returns the mutable type `T`. + +### as_slice + +#include_code as_slice noir_stdlib/src/meta/unresolved_type.nr rust + +If this is a slice `&[T]`, returns the element type `T`. + +### is_bool + +#include_code is_bool noir_stdlib/src/meta/unresolved_type.nr rust + +Returns `true` if this type is `bool`. + ### is_field #include_code is_field noir_stdlib/src/meta/unresolved_type.nr rust Returns true if this type refers to the Field type. + +### is_unit + +#include_code is_unit noir_stdlib/src/meta/unresolved_type.nr rust + +Returns true if this type is the unit `()` type. diff --git a/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx deleted file mode 100644 index 60414a2fa51..00000000000 --- a/noir/noir-repo/docs/docs/noir/standard_library/recursion.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Recursive Proofs -description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, verify_proof] ---- - -import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; - -Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. - -Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) - -## The `#[recursive]` Attribute - -In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. - -### Example usage with `#[recursive]` - -```rust -#[recursive] -fn main(x: Field, y: pub Field) { - assert(x == y, "x and y are not equal"); -} - -// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit -// are intended for recursive verification. -``` - -By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. - -## Verifying Recursive Proofs - -```rust -#[foreign(recursive_aggregation)] -pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} -``` - - - -## Example usage - -```rust - -fn main( - verification_key : [Field; 114], - proof : [Field; 93], - public_inputs : [Field; 1], - key_hash : Field, - proof_b : [Field; 93], -) { - std::verify_proof( - verification_key, - proof, - public_inputs, - key_hash - ); - - std::verify_proof( - verification_key, - proof_b, - public_inputs, - key_hash - ); -} -``` - -You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). - -## Parameters - -### `verification_key` - -The verification key for the zk program that is being verified. - -### `proof` - -The proof for the zk program that is being verified. - -### `public_inputs` - -These represent the public inputs of the proof we are verifying. - -### `key_hash` - -A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx deleted file mode 100644 index 60414a2fa51..00000000000 --- a/noir/noir-repo/docs/versioned_docs/version-v0.33.0/noir/standard_library/recursion.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Recursive Proofs -description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, verify_proof] ---- - -import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; - -Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. - -Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) - -## The `#[recursive]` Attribute - -In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. - -### Example usage with `#[recursive]` - -```rust -#[recursive] -fn main(x: Field, y: pub Field) { - assert(x == y, "x and y are not equal"); -} - -// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit -// are intended for recursive verification. -``` - -By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. - -## Verifying Recursive Proofs - -```rust -#[foreign(recursive_aggregation)] -pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} -``` - - - -## Example usage - -```rust - -fn main( - verification_key : [Field; 114], - proof : [Field; 93], - public_inputs : [Field; 1], - key_hash : Field, - proof_b : [Field; 93], -) { - std::verify_proof( - verification_key, - proof, - public_inputs, - key_hash - ); - - std::verify_proof( - verification_key, - proof_b, - public_inputs, - key_hash - ); -} -``` - -You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). - -## Parameters - -### `verification_key` - -The verification key for the zk program that is being verified. - -### `proof` - -The proof for the zk program that is being verified. - -### `public_inputs` - -These represent the public inputs of the proof we are verifying. - -### `key_hash` - -A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx deleted file mode 100644 index 60414a2fa51..00000000000 --- a/noir/noir-repo/docs/versioned_docs/version-v0.34.0/noir/standard_library/recursion.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Recursive Proofs -description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, verify_proof] ---- - -import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; - -Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. - -Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) - -## The `#[recursive]` Attribute - -In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. - -### Example usage with `#[recursive]` - -```rust -#[recursive] -fn main(x: Field, y: pub Field) { - assert(x == y, "x and y are not equal"); -} - -// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit -// are intended for recursive verification. -``` - -By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. - -## Verifying Recursive Proofs - -```rust -#[foreign(recursive_aggregation)] -pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} -``` - - - -## Example usage - -```rust - -fn main( - verification_key : [Field; 114], - proof : [Field; 93], - public_inputs : [Field; 1], - key_hash : Field, - proof_b : [Field; 93], -) { - std::verify_proof( - verification_key, - proof, - public_inputs, - key_hash - ); - - std::verify_proof( - verification_key, - proof_b, - public_inputs, - key_hash - ); -} -``` - -You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). - -## Parameters - -### `verification_key` - -The verification key for the zk program that is being verified. - -### `proof` - -The proof for the zk program that is being verified. - -### `public_inputs` - -These represent the public inputs of the proof we are verifying. - -### `key_hash` - -A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/recursion.mdx b/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/recursion.mdx deleted file mode 100644 index 60414a2fa51..00000000000 --- a/noir/noir-repo/docs/versioned_docs/version-v0.35.0/noir/standard_library/recursion.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: Recursive Proofs -description: Learn about how to write recursive proofs in Noir. -keywords: [recursion, recursive proofs, verification_key, verify_proof] ---- - -import BlackBoxInfo from '@site/src/components/Notes/_blackbox'; - -Noir supports recursively verifying proofs, meaning you verify the proof of a Noir program in another Noir program. This enables creating proofs of arbitrary size by doing step-wise verification of smaller components of a large proof. - -Read [the explainer on recursion](../../explainers/explainer-recursion.md) to know more about this function and the [guide on how to use it.](../../how_to/how-to-recursion.md) - -## The `#[recursive]` Attribute - -In Noir, the `#[recursive]` attribute is used to indicate that a circuit is designed for recursive proof generation. When applied, it informs the compiler and the tooling that the circuit should be compiled in a way that makes its proofs suitable for recursive verification. This attribute eliminates the need for manual flagging of recursion at the tooling level, streamlining the proof generation process for recursive circuits. - -### Example usage with `#[recursive]` - -```rust -#[recursive] -fn main(x: Field, y: pub Field) { - assert(x == y, "x and y are not equal"); -} - -// This marks the circuit as recursion-friendly and indicates that proofs generated from this circuit -// are intended for recursive verification. -``` - -By incorporating this attribute directly in the circuit's definition, tooling like Nargo and NoirJS can automatically execute recursive-specific duties for Noir programs (e.g. recursive-friendly proof artifact generation) without additional flags or configurations. - -## Verifying Recursive Proofs - -```rust -#[foreign(recursive_aggregation)] -pub fn verify_proof(verification_key: [Field], proof: [Field], public_inputs: [Field], key_hash: Field) {} -``` - - - -## Example usage - -```rust - -fn main( - verification_key : [Field; 114], - proof : [Field; 93], - public_inputs : [Field; 1], - key_hash : Field, - proof_b : [Field; 93], -) { - std::verify_proof( - verification_key, - proof, - public_inputs, - key_hash - ); - - std::verify_proof( - verification_key, - proof_b, - public_inputs, - key_hash - ); -} -``` - -You can see a full example of recursive proofs in [this example recursion demo repo](https://github.com/noir-lang/noir-examples/tree/master/recursion). - -## Parameters - -### `verification_key` - -The verification key for the zk program that is being verified. - -### `proof` - -The proof for the zk program that is being verified. - -### `public_inputs` - -These represent the public inputs of the proof we are verifying. - -### `key_hash` - -A key hash is used to check the validity of the verification key. The circuit implementing this opcode can use this hash to ensure that the key provided to the circuit matches the key produced by the circuit creator. diff --git a/noir/noir-repo/noir_stdlib/src/meta/typ.nr b/noir/noir-repo/noir_stdlib/src/meta/typ.nr index 50f8fa60fb9..31fdbb49b53 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/typ.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/typ.nr @@ -1,69 +1,195 @@ +//! Contains methods on the built-in `Type` type used for representing a type in the source program. + use crate::cmp::Eq; use crate::option::Option; +/// Creates and returns an unbound type variable. This is a special kind of type internal +/// to type checking which will type check with any other type. When it is type checked +/// against another type it will also be set to that type. For example, if `a` is a type +/// variable and we have the type equality `(a, i32) = (u8, i32)`, the compiler will set +/// `a` equal to `u8`. +/// +/// Unbound type variables will often be rendered as `_` while printing them. Bound type +/// variables will appear as the type they are bound to. +/// +/// This can be used in conjunction with functions which internally perform type checks +/// such as `Type::implements` or `Type::get_trait_impl` to potentially grab some of the types used. +/// +/// Note that calling `Type::implements` or `Type::get_trait_impl` on a type variable will always +/// fail. +/// +/// Example: +/// +/// ```noir +/// trait Serialize {} +/// +/// impl Serialize<1> for Field {} +/// +/// impl Serialize for [T; N] +/// where T: Serialize {} +/// +/// impl Serialize for (T, U) +/// where T: Serialize, U: Serialize {} +/// +/// fn fresh_variable_example() { +/// let typevar1 = std::meta::typ::fresh_type_variable(); +/// let constraint = quote { Serialize<$typevar1> }.as_trait_constraint(); +/// let field_type = quote { Field }.as_type(); +/// +/// // Search for a trait impl (binding typevar1 to 1 when the impl is found): +/// assert(field_type.implements(constraint)); +/// +/// // typevar1 should be bound to the "1" generic now: +/// assert_eq(typevar1.as_constant().unwrap(), 1); +/// +/// // If we want to do the same with a different type, we need to +/// // create a new type variable now that `typevar1` is bound +/// let typevar2 = std::meta::typ::fresh_type_variable(); +/// let constraint = quote { Serialize<$typevar2> }.as_trait_constraint(); +/// let array_type = quote { [(Field, Field); 5] }.as_type(); +/// assert(array_type.implements(constraint)); +/// +/// // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 +/// assert_eq(typevar2.as_constant().unwrap(), 10); +/// } +/// ``` #[builtin(fresh_type_variable)] // docs:start:fresh_type_variable pub comptime fn fresh_type_variable() -> Type {} // docs:end:fresh_type_variable impl Type { + /// If this type is an array, return a pair of (element type, size type). + /// + /// Example: + /// + /// ```noir + /// comptime { + /// let array_type = quote { [Field; 3] }.as_type(); + /// let (field_type, three_type) = array_type.as_array().unwrap(); + /// + /// assert(field_type.is_field()); + /// assert_eq(three_type.as_constant().unwrap(), 3); + /// } + /// ``` #[builtin(type_as_array)] // docs:start:as_array pub comptime fn as_array(self) -> Option<(Type, Type)> {} // docs:end:as_array + /// If this type is a constant integer (such as the `3` in the array type `[Field; 3]`), + /// return the numeric constant. #[builtin(type_as_constant)] // docs:start:as_constant pub comptime fn as_constant(self) -> Option {} // docs:end:as_constant + /// If this is an integer type, return a boolean which is `true` + /// if the type is signed, as well as the number of bits of this integer type. #[builtin(type_as_integer)] // docs:start:as_integer pub comptime fn as_integer(self) -> Option<(bool, u8)> {} // docs:end:as_integer + /// If this is a mutable reference type `&mut T`, returns the mutable type `T`. + #[builtin(type_as_mutable_reference)] + // docs:start:as_mutable_reference + comptime fn as_mutable_reference(self) -> Option {} + // docs:end:as_mutable_reference + + /// If this is a slice type, return the element type of the slice. #[builtin(type_as_slice)] // docs:start:as_slice pub comptime fn as_slice(self) -> Option {} // docs:end:as_slice + /// If this is a `str` type, returns the length `N` as a type. #[builtin(type_as_str)] // docs:start:as_str pub comptime fn as_str(self) -> Option {} // docs:end:as_str + /// If this is a struct type, returns the struct in addition to any generic arguments on this type. #[builtin(type_as_struct)] // docs:start:as_struct pub comptime fn as_struct(self) -> Option<(StructDefinition, [Type])> {} // docs:end:as_struct + /// If this is a tuple type, returns each element type of the tuple. #[builtin(type_as_tuple)] // docs:start:as_tuple pub comptime fn as_tuple(self) -> Option<[Type]> {} // docs:end:as_tuple + /// Retrieves the trait implementation that implements the given + /// trait constraint for this type. If the trait constraint is not + /// found, `None` is returned. Note that since the concrete trait implementation + /// for a trait constraint specified from a `where` clause is unknown, + /// this function will return `None` in these cases. If you only want to know + /// whether a type implements a trait, use `implements` instead. + /// + /// Example: + /// + /// ```rust + /// comptime { + /// let field_type = quote { Field }.as_type(); + /// let default = quote { Default }.as_trait_constraint(); + /// + /// let the_impl: TraitImpl = field_type.get_trait_impl(default).unwrap(); + /// assert(the_impl.methods().len(), 1); + /// } + /// ``` #[builtin(type_get_trait_impl)] // docs:start:get_trait_impl pub comptime fn get_trait_impl(self, constraint: TraitConstraint) -> Option {} // docs:end:get_trait_impl + /// Returns `true` if this type implements the given trait. Note that unlike + /// `get_trait_impl` this will also return true for any `where` constraints + /// in scope. + /// + /// Example: + /// + /// ```rust + /// fn foo() where T: Default { + /// comptime { + /// let field_type = quote { Field }.as_type(); + /// let default = quote { Default }.as_trait_constraint(); + /// assert(field_type.implements(default)); + /// + /// let t = quote { T }.as_type(); + /// assert(t.implements(default)); + /// } + /// } + /// ``` #[builtin(type_implements)] // docs:start:implements pub comptime fn implements(self, constraint: TraitConstraint) -> bool {} // docs:end:implements + /// Returns `true` if this type is `bool`. #[builtin(type_is_bool)] // docs:start:is_bool pub comptime fn is_bool(self) -> bool {} // docs:end:is_bool + /// Returns `true` if this type is `Field`. #[builtin(type_is_field)] -// docs:start:is_field + // docs:start:is_field pub comptime fn is_field(self) -> bool {} // docs:end:is_field + + /// Returns `true` if this type is the unit `()` type. + #[builtin(type_is_unit)] + // docs:start:is_unit + comptime fn is_unit(self) -> bool {} + // docs:end:is_unit } impl Eq for Type { + /// Note that this is syntactic equality, this is not the same as whether two types will type check + /// to be the same type. Unless type inference or generics are being used however, users should not + /// typically have to worry about this distinction. comptime fn eq(self, other: Self) -> bool { type_eq(self, other) } diff --git a/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr b/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr index c6e6d396546..5c2e8803674 100644 --- a/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr +++ b/noir/noir-repo/noir_stdlib/src/meta/unresolved_type.nr @@ -1,6 +1,35 @@ +//! Contains methods on the built-in `UnresolvedType` type for the syntax of types. + +use crate::option::Option; + impl UnresolvedType { + /// If this is a mutable reference type `&mut T`, returns the mutable type `T`. + #[builtin(unresolved_type_as_mutable_reference)] + // docs:start:as_mutable_reference + comptime fn as_mutable_reference(self) -> Option {} + // docs:end:as_mutable_reference + + /// If this is a slice `&[T]`, returns the element type `T`. + #[builtin(unresolved_type_as_slice)] + // docs:start:as_slice + comptime fn as_slice(self) -> Option {} + // docs:end:as_slice + + /// Returns `true` if this type is `bool`. + #[builtin(unresolved_type_is_bool)] + // docs:start:is_bool + comptime fn is_bool(self) -> bool {} + // docs:end:is_bool + + /// Returns true if this type refers to the `Field` type. #[builtin(unresolved_type_is_field)] // docs:start:is_field pub comptime fn is_field(self) -> bool {} // docs:end:is_field + + /// Returns true if this type is the unit `()` type. + #[builtin(unresolved_type_is_unit)] + // docs:start:is_unit + comptime fn is_unit(self) -> bool {} + // docs:end:is_unit } diff --git a/noir/noir-repo/noir_stdlib/src/schnorr.nr b/noir/noir-repo/noir_stdlib/src/schnorr.nr index e24aabf3cda..0623f116dea 100644 --- a/noir/noir-repo/noir_stdlib/src/schnorr.nr +++ b/noir/noir-repo/noir_stdlib/src/schnorr.nr @@ -41,6 +41,8 @@ pub fn verify_signature_noir( for i in 0..32 { is_ok &= result[i] == signature[32 + i]; } + } else { + is_ok = false; } is_ok } @@ -92,3 +94,12 @@ fn calculate_signature_challenge( let result = crate::hash::blake2s(hash_input); (r.is_infinite, result) } + +#[test] +fn test_zero_signature() { + let public_key: EmbeddedCurvePoint = EmbeddedCurvePoint { x: 1, y: 17631683881184975370165255887551781615748388533673675138860, is_infinite: false }; + let signature: [u8; 64] = [0; 64]; + let message: [u8; _] = [2; 64]; // every message + let verified = verify_signature_noir(public_key, signature, message); + assert(!verified); +} diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr index c7b3d0b9400..2b1bd215960 100644 --- a/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_type/src/main.nr @@ -148,6 +148,14 @@ fn main() { // Now typevar2 should be bound to the serialized pair size 2 times the array length 5 assert_eq(typevar2.as_constant().unwrap(), 10); // docs:end:fresh-type-variable-example + + // Check Type::is_unit + let unit = quote { () }.as_type(); + assert(unit.is_unit()); + + // Check Type::as_mutable_reference + let typ = quote { &mut Field }.as_type(); + assert_eq(typ.as_mutable_reference().unwrap(), quote { Field }.as_type()); } } diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml new file mode 100644 index 00000000000..cc266b9b9c5 --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "comptime_unresolved_type" +type = "bin" +authors = [""] +compiler_version = ">=0.31.0" + +[dependencies] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr new file mode 100644 index 00000000000..aba4461d4af --- /dev/null +++ b/noir/noir-repo/test_programs/compile_success_empty/comptime_unresolved_type/src/main.nr @@ -0,0 +1,24 @@ +fn main() { + comptime + { + // Check UnresolvedType::is_bool + let typ = quote { x as bool }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_bool()); + + // Check UnresolvedType::is_field + let typ = quote { x as Field }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_field()); + + // Check UnresolvedType::is_unit + let typ = quote { x as () }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.is_unit()); + + // Check UnresolvedType::as_mutable_reference + let typ = quote { x as &mut Field }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.as_mutable_reference().unwrap().is_field()); + + // Check UnresolvedType::as_slice + let typ = quote { x as [Field] }.as_expr().unwrap().as_cast().unwrap().1; + assert(typ.as_slice().unwrap().is_field()); + } +} diff --git a/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Nargo.toml b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Nargo.toml new file mode 100644 index 00000000000..38c916e5d97 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Nargo.toml @@ -0,0 +1,6 @@ +[package] +name = "databus_composite_calldata" +type = "bin" +authors = [""] + +[dependencies] diff --git a/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Prover.toml b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Prover.toml new file mode 100644 index 00000000000..ab154c13372 --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/Prover.toml @@ -0,0 +1,11 @@ +zero = "0" +one = "1" +values = [[["12", "33"], ["37", "11"]],[["14", "37"], ["30", "10"]],[["10", "30"], ["30", "10"]]] + +[[foos]] +x = 1 +y = [1,2,3,4,5,6,7,8,9,0] + +[[foos]] +x = 2 +y = [1,2,3,5,6,8,7,8,9,0] \ No newline at end of file diff --git a/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/src/main.nr b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/src/main.nr new file mode 100644 index 00000000000..e8b88e84d0d --- /dev/null +++ b/noir/noir-repo/test_programs/execution_success/databus_composite_calldata/src/main.nr @@ -0,0 +1,16 @@ +struct Foo { + x: u32, + y: [u32; 10], +} + +fn main( + foos: call_data(0) [Foo; 2], + values: call_data(0) [[[u32; 2]; 2]; 3], + zero: u32, + one: u32 +) -> pub u32 { + assert_eq(foos[zero].x + 1, foos[one].x); + assert_eq(foos[zero].y[3] + 2, foos[one].y[4]); + assert_eq(values[zero][one][zero], values[one][zero][one]); + foos[zero].x + foos[one].y[0] +} diff --git a/noir/noir-repo/tooling/lsp/Cargo.toml b/noir/noir-repo/tooling/lsp/Cargo.toml index 209f2afe4a4..c15895d801f 100644 --- a/noir/noir-repo/tooling/lsp/Cargo.toml +++ b/noir/noir-repo/tooling/lsp/Cargo.toml @@ -14,7 +14,6 @@ workspace = true [dependencies] acvm.workspace = true -chumsky.workspace = true codespan-lsp.workspace = true lsp-types.workspace = true nargo.workspace = true diff --git a/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs b/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs index 0387d35d41f..39e1385a6e8 100644 --- a/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs +++ b/noir/noir-repo/tooling/lsp/src/attribute_reference_finder.rs @@ -6,7 +6,6 @@ /// will give not only the attribute function but also any type generated by it. use std::collections::BTreeMap; -use chumsky::Parser; use fm::FileId; use noirc_errors::Span; use noirc_frontend::{ @@ -16,9 +15,8 @@ use noirc_frontend::{ def_map::{CrateDefMap, LocalModuleId, ModuleId}, resolution::path_resolver::{PathResolver, StandardPathResolver}, }, - lexer::Lexer, node_interner::ReferenceId, - parser::{path_no_turbofish, ParsedSubModule}, + parser::{ParsedSubModule, Parser}, token::CustomAttribute, usage_tracker::UsageTracker, ParsedModule, @@ -96,10 +94,8 @@ impl<'a> Visitor for AttributeReferenceFinder<'a> { Some((left, _right)) => left.to_string(), None => attribute.contents.to_string(), }; - let (tokens, _) = Lexer::lex(&name); - - let parser = path_no_turbofish(); - let Ok(path) = parser.parse(tokens) else { + let mut parser = Parser::for_str(&name); + let Some(path) = parser.parse_path_no_turbofish() else { return; }; diff --git a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs index e4beea48064..bc8bb75e10c 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/completion/tests.rs @@ -595,7 +595,7 @@ mod completion_tests { vec![simple_completion_item( "lambda_var", CompletionItemKind::VARIABLE, - Some("_".to_string()), + Some("i32".to_string()), )], ) .await; diff --git a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs index 6c41f4dc2e5..0fc2dc4622e 100644 --- a/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs +++ b/noir/noir-repo/tooling/lsp/src/requests/document_symbol.rs @@ -66,6 +66,10 @@ impl<'a> DocumentSymbolCollector<'a> { } fn collect_in_type(&mut self, name: &Ident, typ: Option<&UnresolvedType>) { + if name.0.contents.is_empty() { + return; + } + let Some(name_location) = self.to_lsp_location(name.span()) else { return; }; @@ -99,6 +103,10 @@ impl<'a> DocumentSymbolCollector<'a> { typ: &UnresolvedType, default_value: Option<&Expression>, ) { + if name.0.contents.is_empty() { + return; + } + let Some(name_location) = self.to_lsp_location(name.span()) else { return; }; @@ -137,6 +145,10 @@ impl<'a> DocumentSymbolCollector<'a> { impl<'a> Visitor for DocumentSymbolCollector<'a> { fn visit_noir_function(&mut self, noir_function: &NoirFunction, span: Span) -> bool { + if noir_function.def.name.0.contents.is_empty() { + return false; + } + let Some(location) = self.to_lsp_location(span) else { return false; }; @@ -162,6 +174,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_noir_struct(&mut self, noir_struct: &NoirStruct, span: Span) -> bool { + if noir_struct.name.0.contents.is_empty() { + return false; + } + let Some(location) = self.to_lsp_location(span) else { return false; }; @@ -213,6 +229,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_noir_trait(&mut self, noir_trait: &NoirTrait, span: Span) -> bool { + if noir_trait.name.0.contents.is_empty() { + return false; + } + let Some(location) = self.to_lsp_location(span) else { return false; }; @@ -255,6 +275,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { _where_clause: &[noirc_frontend::ast::UnresolvedTraitConstraint], body: &Option, ) -> bool { + if name.0.contents.is_empty() { + return false; + } + let Some(name_location) = self.to_lsp_location(name.span()) else { return false; }; @@ -308,6 +332,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { typ: &UnresolvedType, default_value: &Option, ) -> bool { + if name.0.contents.is_empty() { + return false; + } + self.collect_in_constant(name, typ, default_value.as_ref()); false } @@ -400,6 +428,9 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { }; let name = type_impl.object_type.typ.to_string(); + if name.is_empty() { + return false; + } let Some(name_location) = self.to_lsp_location(type_impl.object_type.span) else { return false; @@ -431,6 +462,10 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_parsed_submodule(&mut self, parsed_sub_module: &ParsedSubModule, span: Span) -> bool { + if parsed_sub_module.name.0.contents.is_empty() { + return false; + } + let Some(name_location) = self.to_lsp_location(parsed_sub_module.name.span()) else { return false; }; @@ -465,6 +500,11 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { } fn visit_global(&mut self, global: &LetStatement, span: Span) -> bool { + let name = global.pattern.to_string(); + if name.is_empty() { + return false; + } + let Some(name_location) = self.to_lsp_location(global.pattern.span()) else { return false; }; @@ -475,7 +515,7 @@ impl<'a> Visitor for DocumentSymbolCollector<'a> { #[allow(deprecated)] self.symbols.push(DocumentSymbol { - name: global.pattern.to_string(), + name, detail: None, kind: SymbolKind::CONSTANT, tags: None, @@ -634,7 +674,7 @@ mod document_symbol_tests { deprecated: None, range: Range { start: Position { line: 15, character: 7 }, - end: Position { line: 15, character: 25 }, + end: Position { line: 15, character: 24 }, }, selection_range: Range { start: Position { line: 15, character: 7 }, diff --git a/noir/noir-repo/tooling/nargo_cli/Cargo.toml b/noir/noir-repo/tooling/nargo_cli/Cargo.toml index 284be56d247..acd5623871e 100644 --- a/noir/noir-repo/tooling/nargo_cli/Cargo.toml +++ b/noir/noir-repo/tooling/nargo_cli/Cargo.toml @@ -45,7 +45,12 @@ prettytable-rs = "0.10" rayon.workspace = true thiserror.workspace = true tower.workspace = true -async-lsp = { workspace = true, features = ["client-monitor", "stdio", "tracing", "tokio"] } +async-lsp = { workspace = true, features = [ + "client-monitor", + "stdio", + "tracing", + "tokio", +] } const_format.workspace = true similar-asserts.workspace = true termcolor = "1.1.2" @@ -75,9 +80,13 @@ fm.workspace = true criterion.workspace = true pprof.workspace = true paste = "1.0.14" +proptest.workspace = true +sha2.workspace = true +sha3.workspace = true iai = "0.1.1" test-binary = "3.0.2" + [[bench]] name = "criterion" harness = false diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions new file mode 100644 index 00000000000..ab88db8b6c2 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.proptest-regressions @@ -0,0 +1,7 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc 88db0227d5547742f771c14b1679f6783570b46bf7cf9e6897ee1aca4bd5034d # shrinks to io = SnippetInputOutput { description: "force_brillig = false, max_len = 200", inputs: {"input": Vec([Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(1), Field(5), Field(20), Field(133), Field(233), Field(99), Field(2⁶), Field(196), Field(232), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0), Field(0)]), "message_size": Field(2⁶)}, expected_output: Vec([Field(102), Field(26), Field(94), Field(212), Field(102), Field(1), Field(215), Field(217), Field(167), Field(175), Field(158), Field(18), Field(20), Field(244), Field(158), Field(200), Field(2⁷), Field(186), Field(251), Field(243), Field(20), Field(207), Field(22), Field(3), Field(139), Field(81), Field(207), Field(2⁴), Field(50), Field(167), Field(1), Field(163)]) } diff --git a/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs new file mode 100644 index 00000000000..d1ea5bbfaf6 --- /dev/null +++ b/noir/noir-repo/tooling/nargo_cli/tests/stdlib-props.rs @@ -0,0 +1,261 @@ +use std::{cell::RefCell, collections::BTreeMap, path::Path}; + +use acvm::{acir::native_types::WitnessStack, FieldElement}; +use nargo::{ + ops::{execute_program, DefaultForeignCallExecutor}, + parse_all, +}; +use noirc_abi::input_parser::InputValue; +use noirc_driver::{ + compile_main, file_manager_with_stdlib, prepare_crate, CompilationResult, CompileOptions, + CompiledProgram, CrateId, +}; +use noirc_frontend::hir::Context; +use proptest::prelude::*; +use sha3::Digest; + +/// Inputs and expected output of a snippet encoded in ABI format. +#[derive(Debug)] +struct SnippetInputOutput { + description: String, + inputs: BTreeMap, + expected_output: InputValue, +} +impl SnippetInputOutput { + fn new(inputs: Vec<(&str, InputValue)>, output: InputValue) -> Self { + Self { + description: "".to_string(), + inputs: inputs.into_iter().map(|(k, v)| (k.to_string(), v)).collect(), + expected_output: output, + } + } + + /// Attach some description to hint at the scenario we are testing. + fn with_description(mut self, description: String) -> Self { + self.description = description; + self + } +} + +/// Prepare a code snippet. +fn prepare_snippet(source: String) -> (Context<'static, 'static>, CrateId) { + let root = Path::new(""); + let file_name = Path::new("main.nr"); + let mut file_manager = file_manager_with_stdlib(root); + file_manager.add_file_with_source(file_name, source).expect( + "Adding source buffer to file manager should never fail when file manager is empty", + ); + let parsed_files = parse_all(&file_manager); + + let mut context = Context::new(file_manager, parsed_files); + let root_crate_id = prepare_crate(&mut context, file_name); + + (context, root_crate_id) +} + +/// Compile the main function in a code snippet. +/// +/// Use `force_brillig` to test it as an unconstrained function without having to change the code. +/// This is useful for methods that use the `runtime::is_unconstrained()` method to change their behavior. +fn prepare_and_compile_snippet( + source: String, + force_brillig: bool, +) -> CompilationResult { + let (mut context, root_crate_id) = prepare_snippet(source); + let options = CompileOptions { force_brillig, ..Default::default() }; + compile_main(&mut context, root_crate_id, &options, None) +} + +/// Compile a snippet and run property tests against it by generating random input/output pairs +/// according to the strategy, executing the snippet with the input, and asserting that the +/// output it returns is the one we expect. +fn run_snippet_proptest( + source: String, + force_brillig: bool, + strategy: BoxedStrategy, +) { + let program = match prepare_and_compile_snippet(source.clone(), force_brillig) { + Ok((program, _)) => program, + Err(e) => panic!("failed to compile program:\n{source}\n{e:?}"), + }; + + let blackbox_solver = bn254_blackbox_solver::Bn254BlackBoxSolver; + let foreign_call_executor = + RefCell::new(DefaultForeignCallExecutor::new(false, None, None, None)); + + // Generate multiple input/output + proptest!(ProptestConfig::with_cases(100), |(io in strategy)| { + let initial_witness = program.abi.encode(&io.inputs, None).expect("failed to encode"); + let mut foreign_call_executor = foreign_call_executor.borrow_mut(); + + let witness_stack: WitnessStack = execute_program( + &program.program, + initial_witness, + &blackbox_solver, + &mut *foreign_call_executor, + ) + .expect("failed to execute"); + + let main_witness = witness_stack.peek().expect("should have return value on witness stack"); + let main_witness = &main_witness.witness; + + let (_, return_value) = program.abi.decode(main_witness).expect("failed to decode"); + let return_value = return_value.expect("should decode a return value"); + + prop_assert_eq!(return_value, io.expected_output, "{}", io.description); + }); +} + +/// Run property tests on a code snippet which is assumed to execute a hashing function with the following signature: +/// +/// ```ignore +/// fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] +/// ``` +/// +/// The calls are executed with and without forcing brillig, because it seems common for hash functions to run different +/// code paths based on `runtime::is_unconstrained()`. +fn run_hash_proptest( + // Different generic maximum input sizes to try. + max_lengths: &[usize], + // Some hash functions allow inputs which are less than the generic parameters, others don't. + variable_length: bool, + // Make the source code specialized for a given expected input size. + source: impl Fn(usize) -> String, + // Rust implementation of the hash function. + hash: fn(&[u8]) -> [u8; N], +) { + for max_len in max_lengths { + let max_len = *max_len; + // The maximum length is used to pick the generic version of the method. + let source = source(max_len); + // Hash functions runs differently depending on whether the code is unconstrained or not. + for force_brillig in [false, true] { + let length_strategy = + if variable_length { (0..=max_len).boxed() } else { Just(max_len).boxed() }; + // The actual input length can be up to the maximum. + let strategy = length_strategy + .prop_flat_map(|len| prop::collection::vec(any::(), len)) + .prop_map(move |mut msg| { + // The output is the hash of the data as it is. + let output = hash(&msg); + + // The input has to be padded to the maximum length. + let msg_size = msg.len(); + msg.resize(max_len, 0u8); + + let mut inputs = vec![("input", bytes_input(&msg))]; + + // Omit the `message_size` if the hash function doesn't support it. + if variable_length { + inputs.push(( + "message_size", + InputValue::Field(FieldElement::from(msg_size)), + )); + } + + SnippetInputOutput::new(inputs, bytes_input(&output)).with_description(format!( + "force_brillig = {force_brillig}, max_len = {max_len}" + )) + }) + .boxed(); + + run_snippet_proptest(source.clone(), force_brillig, strategy); + } + } +} + +/// This is just a simple test to check that property testing works. +#[test] +fn fuzz_basic() { + let program = "fn main(init: u32) -> pub u32 { + let mut x = init; + for i in 0 .. 6 { + x += i; + } + x + }"; + + let strategy = any::() + .prop_map(|init| { + let init = init / 2; + SnippetInputOutput::new( + vec![("init", InputValue::Field(init.into()))], + InputValue::Field((init + 15).into()), + ) + }) + .boxed(); + + run_snippet_proptest(program.to_string(), false, strategy); +} + +#[test] +fn fuzz_keccak256_equivalence() { + run_hash_proptest( + // XXX: Currently it fails with inputs >= 135 bytes + &[0, 1, 100, 134], + true, + |max_len| { + format!( + "fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] {{ + std::hash::keccak256(input, message_size) + }}" + ) + }, + |data| sha3::Keccak256::digest(data).try_into().unwrap(), + ); +} + +#[test] +#[should_panic] // Remove once fixed +fn fuzz_keccak256_equivalence_over_135() { + run_hash_proptest( + &[135, 150], + true, + |max_len| { + format!( + "fn main(input: [u8; {max_len}], message_size: u32) -> pub [u8; 32] {{ + std::hash::keccak256(input, message_size) + }}" + ) + }, + |data| sha3::Keccak256::digest(data).try_into().unwrap(), + ); +} + +#[test] +fn fuzz_sha256_equivalence() { + run_hash_proptest( + &[0, 1, 200, 511, 512], + true, + |max_len| { + format!( + "fn main(input: [u8; {max_len}], message_size: u64) -> pub [u8; 32] {{ + std::hash::sha256_var(input, message_size) + }}" + ) + }, + |data| sha2::Sha256::digest(data).try_into().unwrap(), + ); +} + +#[test] +fn fuzz_sha512_equivalence() { + run_hash_proptest( + &[0, 1, 200], + false, + |max_len| { + format!( + "fn main(input: [u8; {max_len}]) -> pub [u8; 64] {{ + std::hash::sha512::digest(input) + }}" + ) + }, + |data| sha2::Sha512::digest(data).try_into().unwrap(), + ); +} + +fn bytes_input(bytes: &[u8]) -> InputValue { + InputValue::Vec( + bytes.iter().map(|b| InputValue::Field(FieldElement::from(*b as u32))).collect(), + ) +} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr index 60934b60b2f..0e9761ed52d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/databus.nr @@ -1,2 +1,2 @@ -fn main(x: pub u8, y: call_data u8) -> return_data u32 {} +fn main(x: pub u8, y: call_data(0) u8) -> return_data u32 {} diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr b/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr index 3c2fa42837a..84394f6fa1d 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/expected/impl.nr @@ -10,6 +10,12 @@ impl MyType { fn method(mut self) {} fn method(&mut self) {} + + fn method(self: Self) {} + + fn method(mut self: Self) {} + + fn method(&mut self: Self) {} } impl MyType { diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr index 60934b60b2f..e47fcc50210 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/databus.nr @@ -1,2 +1,2 @@ -fn main(x: pub u8, y: call_data u8) -> return_data u32 {} +fn main(x: pub u8, y: call_data(0) u8) -> return_data u32 { } diff --git a/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr b/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr index e4adb8ebd6a..53c9759ca1b 100644 --- a/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr +++ b/noir/noir-repo/tooling/nargo_fmt/tests/input/impl.nr @@ -10,6 +10,12 @@ impl MyType { fn method(mut self) {} fn method(&mut self) {} + + fn method(self: Self) {} + + fn method(mut self: Self) {} + + fn method(&mut self: Self) {} } impl MyType {