diff --git a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs index 59aaa7948d6..fd9700a2c44 100644 --- a/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs +++ b/compiler/noirc_evaluator/src/ssa/ssa_gen/mod.rs @@ -774,7 +774,7 @@ impl FunctionContext<'_> { } fn codegen_match(&mut self, match_expr: &ast::Match) -> Result { - let variable = self.lookup(match_expr.variable_to_match); + let variable = self.lookup(match_expr.variable_to_match.0); // Any matches with only a single case we don't need to check the tag at all. // Note that this includes all matches on struct / tuple values. @@ -962,7 +962,7 @@ impl FunctionContext<'_> { "Expected enum variant to contain a value for each variant argument" ); - for (value, arg) in variant.into_iter().zip(&case.arguments) { + for (value, (arg, _)) in variant.into_iter().zip(&case.arguments) { self.define(*arg, value); } } @@ -978,7 +978,7 @@ impl FunctionContext<'_> { "Expected field length to match constructor argument count" ); - for (value, arg) in fields.into_iter().zip(&case.arguments) { + for (value, (arg, _)) in fields.into_iter().zip(&case.arguments) { self.define(*arg, value); } } diff --git a/compiler/noirc_frontend/src/monomorphization/ast.rs b/compiler/noirc_frontend/src/monomorphization/ast.rs index 541f753aaf9..6d1da108d22 100644 --- a/compiler/noirc_frontend/src/monomorphization/ast.rs +++ b/compiler/noirc_frontend/src/monomorphization/ast.rs @@ -305,7 +305,7 @@ pub struct If { #[derive(Debug, Clone, Hash)] pub struct Match { - pub variable_to_match: LocalId, + pub variable_to_match: (LocalId, String), pub cases: Vec, pub default_case: Option>, pub typ: Type, @@ -314,7 +314,7 @@ pub struct Match { #[derive(Debug, Clone, Hash)] pub struct MatchCase { pub constructor: Constructor, - pub arguments: Vec, + pub arguments: Vec<(LocalId, String)>, pub branch: Expression, } diff --git a/compiler/noirc_frontend/src/monomorphization/mod.rs b/compiler/noirc_frontend/src/monomorphization/mod.rs index 35632c79d28..387cf2e904a 100644 --- a/compiler/noirc_frontend/src/monomorphization/mod.rs +++ b/compiler/noirc_frontend/src/monomorphization/mod.rs @@ -2181,16 +2181,18 @@ impl<'interner> Monomorphizer<'interner> { Ok(ast::Expression::If(ast::If { condition, consequence, alternative, typ })) } HirMatch::Switch(variable_to_match, cases, default) => { - let variable_to_match = match self.lookup_local(variable_to_match) { + let variable_name = self.interner.definition(variable_to_match).name.clone(); + let variable_id = match self.lookup_local(variable_to_match) { Some(Definition::Local(id)) => id, other => unreachable!("Expected match variable to be defined. Found {other:?}"), }; let cases = try_vecmap(cases, |case| { let arguments = vecmap(case.arguments, |arg| { + let arg_name = self.interner.definition(arg).name.clone(); let new_id = self.next_local_id(); self.define_local(arg, new_id); - new_id + (new_id, arg_name) }); let branch = self.match_expr(case.body, expr_id)?; Ok(ast::MatchCase { constructor: case.constructor, arguments, branch }) @@ -2203,7 +2205,7 @@ impl<'interner> Monomorphizer<'interner> { let typ = Self::convert_type(&result_type, location)?; Ok(ast::Expression::Match(ast::Match { - variable_to_match, + variable_to_match: (variable_id, variable_name), cases, default_case, typ, diff --git a/compiler/noirc_frontend/src/monomorphization/printer.rs b/compiler/noirc_frontend/src/monomorphization/printer.rs index edae2dda56a..e3f9873c5ff 100644 --- a/compiler/noirc_frontend/src/monomorphization/printer.rs +++ b/compiler/noirc_frontend/src/monomorphization/printer.rs @@ -70,6 +70,10 @@ impl AstPrinter { self.fmt_ident(name, &Definition::Function(id)) } + fn fmt_match(&self, name: &str, id: LocalId) -> String { + if self.show_id { format!("${}", id.0) } else { self.fmt_local(name, id) } + } + pub fn print_program(&mut self, program: &Program, f: &mut Formatter) -> std::fmt::Result { for (id, global) in &program.globals { self.print_global(id, global, f)?; @@ -437,13 +441,14 @@ impl AstPrinter { match_expr: &super::ast::Match, f: &mut Formatter, ) -> Result<(), std::fmt::Error> { - write!(f, "match ${} {{", match_expr.variable_to_match.0)?; + let (var_id, var_name) = &match_expr.variable_to_match; + write!(f, "match {} {{", self.fmt_match(var_name, *var_id))?; self.indent_level += 1; self.next_line(f)?; for (i, case) in match_expr.cases.iter().enumerate() { write!(f, "{}", case.constructor)?; - let args = vecmap(&case.arguments, |arg| format!("${}", arg.0)).join(", "); + let args = vecmap(&case.arguments, |(id, name)| self.fmt_match(name, *id)).join(", "); if !args.is_empty() { write!(f, "({args})")?; } diff --git a/compiler/noirc_frontend/src/ownership/last_uses.rs b/compiler/noirc_frontend/src/ownership/last_uses.rs index e840b984f1a..2fb8a67071e 100644 --- a/compiler/noirc_frontend/src/ownership/last_uses.rs +++ b/compiler/noirc_frontend/src/ownership/last_uses.rs @@ -381,7 +381,7 @@ impl LastUseContext { let match_id = self.next_if_or_match_id(); for (i, case) in match_expr.cases.iter().enumerate() { - for argument in &case.arguments { + for (argument, _) in &case.arguments { self.declare_variable(*argument); } diff --git a/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_direct.rs b/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_direct.rs index f2b77bd0970..fab9c7d2760 100644 --- a/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_direct.rs +++ b/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_direct.rs @@ -22,6 +22,8 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> { avoid_negative_int_literals: true, // Avoid break/continue avoid_loop_control: true, + // Match is not yet implemented in comptime. + avoid_match: true, // Has to only use expressions valid in comptime comptime_friendly: true, // Force brillig, to generate loops that the interpreter can do but ACIR cannot. diff --git a/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_nargo.rs b/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_nargo.rs index f631c0b0f46..2a5cf131305 100644 --- a/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_nargo.rs +++ b/tooling/ast_fuzzer/fuzz/src/targets/comptime_vs_brillig_nargo.rs @@ -24,6 +24,8 @@ pub fn fuzz(u: &mut Unstructured) -> eyre::Result<()> { avoid_negative_int_literals: true, // Avoid break/continue avoid_loop_control: true, + // Match is not yet implemented in comptime. + avoid_match: true, // Has to only use expressions valid in comptime comptime_friendly: true, // Force brillig, to generate loops that the interpreter can do but ACIR cannot. diff --git a/tooling/ast_fuzzer/fuzz/src/targets/orig_vs_morph.rs b/tooling/ast_fuzzer/fuzz/src/targets/orig_vs_morph.rs index 98d32a57eaa..6a7a914cfd9 100644 --- a/tooling/ast_fuzzer/fuzz/src/targets/orig_vs_morph.rs +++ b/tooling/ast_fuzzer/fuzz/src/targets/orig_vs_morph.rs @@ -554,6 +554,7 @@ mod rules { // Duplicate the expression, then assign new IDs to all variables created in it. let mut alt = expr.clone(); + reassign_ids(vars, &mut alt); expr::replace(expr, |expr| expr::if_else(cond, expr, alt, typ)); @@ -581,8 +582,9 @@ mod rules { && !expr::exists(expr, |expr| { matches!( expr, - Expression::Let(_) // Creating a variable needs a new ID - | Expression::Block(_) // Applying logical operations on blocks would look odd + Expression::Let(_) // Creating a variable needs a new ID + | Expression::Match(_) // Match creates variables which would need new IDs + | Expression::Block(_) // Applying logical operations on blocks would look odd ) }) } else { @@ -762,6 +764,7 @@ mod helpers { /// Types we can consider using in this context. static TYPES: OnceLock> = OnceLock::new(); + /// Assign new IDs to variables and identifiers created in the expression. pub(super) fn reassign_ids(vars: &mut VariableContext, expr: &mut Expression) { fn replace_local_id( vars: &mut VariableContext, @@ -778,8 +781,12 @@ mod helpers { visit_expr_be_mut( expr, + // Assign a new ID where variables are created, and remember what original value they replaced. &mut |expr| { match expr { + Expression::Ident(ident) => { + ident.id = vars.next_ident_id(); + } Expression::Let(let_) => { replace_local_id(vars, &mut replacements.borrow_mut(), &mut let_.id) } @@ -788,14 +795,23 @@ mod helpers { &mut replacements.borrow_mut(), &mut for_.index_variable, ), - Expression::Ident(ident) => { - ident.id = vars.next_ident_id(); + Expression::Match(match_) => { + let mut replacements = replacements.borrow_mut(); + if let Some(replacement) = replacements.get(&match_.variable_to_match.0) { + match_.variable_to_match.0 = *replacement; + } + for case in match_.cases.iter_mut() { + for (arg, _) in case.arguments.iter_mut() { + replace_local_id(vars, &mut replacements, arg); + } + } } _ => (), } (true, ()) }, &mut |_, _| {}, + // Update the IDs in identifiers based on the replacements we remember from above. &mut |ident| { if let Definition::Local(id) = &mut ident.definition { if let Some(replacement) = replacements.borrow().get(id) { diff --git a/tooling/ast_fuzzer/src/lib.rs b/tooling/ast_fuzzer/src/lib.rs index 37b9f068a96..05705b93284 100644 --- a/tooling/ast_fuzzer/src/lib.rs +++ b/tooling/ast_fuzzer/src/lib.rs @@ -45,6 +45,8 @@ pub struct Config { pub vary_loop_size: bool, /// Maximum number of recursive calls to make at runtime. pub max_recursive_calls: usize, + /// Maximum number of match cases. + pub max_match_cases: usize, /// Frequency of expressions, which produce a value. pub expr_freqs: Freqs, /// Frequency of statements in ACIR functions. @@ -70,6 +72,8 @@ pub struct Config { pub avoid_print: bool, /// Avoid using constrain statements. pub avoid_constrain: bool, + /// Avoid match statements and expressions. + pub avoid_match: bool, /// Avoid using the slice type. pub avoid_slices: bool, /// Only use comptime friendly expressions. @@ -82,6 +86,7 @@ impl Default for Config { ("unary", 10), ("binary", 20), ("if", 15), + ("match", 30), ("block", 30), ("vars", 25), ("literal", 5), @@ -90,19 +95,21 @@ impl Default for Config { let stmt_freqs_acir = Freqs::new(&[ ("assign", 30), ("if", 10), - ("for", 22), + ("match", 10), + ("for", 25), ("let", 25), ("call", 5), ("constrain", 4), ]); let stmt_freqs_brillig = Freqs::new(&[ - ("break", 20), - ("continue", 20), + ("break", 25), + ("continue", 25), ("assign", 30), ("if", 10), - ("for", 15), - ("loop", 15), - ("while", 15), + ("match", 15), + ("for", 17), + ("loop", 17), + ("while", 17), ("let", 20), ("call", 5), ("print", 15), @@ -121,6 +128,7 @@ impl Default for Config { max_loop_size: 10, vary_loop_size: true, max_recursive_calls: 25, + max_match_cases: 3, expr_freqs, stmt_freqs_acir, stmt_freqs_brillig, @@ -133,6 +141,7 @@ impl Default for Config { avoid_lambdas: false, avoid_print: false, avoid_constrain: false, + avoid_match: false, avoid_slices: false, comptime_friendly: false, } diff --git a/tooling/ast_fuzzer/src/program/func.rs b/tooling/ast_fuzzer/src/program/func.rs index 700ebb28f69..553979b7961 100644 --- a/tooling/ast_fuzzer/src/program/func.rs +++ b/tooling/ast_fuzzer/src/program/func.rs @@ -1,3 +1,4 @@ +use iter_extended::vecmap; use nargo::errors::Location; use std::{ collections::{BTreeMap, BTreeSet, HashSet}, @@ -8,17 +9,22 @@ use strum::IntoEnumIterator; use arbitrary::{Arbitrary, Unstructured}; use noirc_frontend::{ ast::{IntegerBitSize, UnaryOp}, - hir_def::{self, expr::HirIdent, stmt::HirPattern}, + hir_def::{ + self, + expr::{Constructor, HirIdent}, + stmt::HirPattern, + }, monomorphization::{ append_printable_type_info_for_type, ast::{ ArrayLiteral, Assign, BinaryOp, Call, Definition, Expression, For, FuncId, GlobalId, - Ident, IdentId, Index, InlineType, LValue, Let, Literal, LocalId, Parameters, Program, - Type, While, + Ident, IdentId, Index, InlineType, LValue, Let, Literal, LocalId, Match, MatchCase, + Parameters, Program, Type, While, }, }, node_interner::DefinitionId, shared::{Signedness, Visibility}, + signed_field::SignedField, }; use crate::Config; @@ -497,7 +503,9 @@ impl<'a> FunctionContext<'a> { let allow_if_then = flags.allow_if_then && allow_nested && self.budget > 0 - && !types::contains_reference(typ); + && (self.unconstrained() || !types::contains_reference(typ)); + + let allow_match = allow_if_then && !self.ctx.config.avoid_match; if freq.enabled_when("unary", allow_nested && types::can_unary_return(typ)) { if let Some(expr) = self.gen_unary(u, typ, max_depth)? { @@ -517,6 +525,15 @@ impl<'a> FunctionContext<'a> { return self.gen_if(u, typ, max_depth, flags); } + // Match expressions, returning a value. + // Treating them similarly to if-then-else. + if freq.enabled_when("match", allow_match) { + // It might not be able to generate the type, if we don't have a suitable variable to match on. + if let Some(expr) = self.gen_match(u, typ, max_depth)? { + return Ok(expr); + } + } + // Block of statements returning a value if freq.enabled_when("block", allow_blocks) { return self.gen_block(u, typ); @@ -537,8 +554,6 @@ impl<'a> FunctionContext<'a> { } } - // TODO(#7926): Match - // If nothing else worked out we can always produce a random literal. self.gen_literal(u, typ).map(|expr| (expr, false)) } @@ -1047,6 +1062,12 @@ impl<'a> FunctionContext<'a> { return self.gen_if(u, &Type::Unit, self.max_depth(), Flags::TOP).map(|(e, _)| e); } + if freq.enabled_when("match", self.budget > 1 && !self.ctx.config.avoid_match) { + if let Some((e, _)) = self.gen_match(u, &Type::Unit, self.max_depth())? { + return Ok(e); + } + } + if freq.enabled_when("for", self.budget > 1) { return self.gen_for(u); } @@ -1654,6 +1675,169 @@ impl<'a> FunctionContext<'a> { } } + /// Generate a `match` expression, returning a given type. + /// + /// Match needs a variable; if we don't have one to produce the target type from, + /// it returns `None`. + fn gen_match( + &mut self, + u: &mut Unstructured, + typ: &Type, + max_depth: usize, + ) -> arbitrary::Result> { + // Decrease the budget so we avoid a potential infinite nesting of match expressions in the rows. + self.decrease_budget(1); + + // Pick a variable that can produce the type we are looking for. + let id = if types::is_unit(typ) { + // If we are generating a statement (return unit), then let's just pick any local variable. + if self.locals.current().is_empty() { + None + } else { + let id = u.choose_iter(self.locals.current().variable_ids())?; + Some(VariableId::Local(*id)) + } + } else { + self.choose_producer(u, typ)? + }; + + // If we have no viable candidate then do something else. + let Some(id) = id else { + return Ok(None); + }; + + // If we picked a global variable, we need to create a local binding first, + // because the match only works with local variable IDs. + let (src_id, src_name, src_typ, src_dyn, src_expr) = match id { + VariableId::Local(id) => { + let (_, name, typ) = self.locals.current().get_variable(&id); + let is_dyn = self.is_dynamic(&id); + (id, name.clone(), typ.clone(), is_dyn, None) + } + VariableId::Global(id) => { + let typ = self.globals.get_variable(&id).2.clone(); + // The source is a technical variable that we don't want to access in the match rows. + let (id, name, let_expr) = self.indirect_global(id, false, false); + (id, name, typ, false, Some(let_expr)) + } + }; + + // We could add some filtering to `choose_producer`, but it's already complicated; maybe next time. + if !types::can_be_matched(&src_typ) { + return Ok(None); + } + + let mut match_expr = Match { + variable_to_match: (src_id, src_name), + cases: vec![], + default_case: None, + typ: typ.clone(), + }; + + let num_cases = u.int_in_range(0..=self.ctx.config.max_match_cases)?; + let mut is_dyn = src_dyn; + + // Generate a number of rows, depending on what we can do with the source type. + // See `MatchCompiler::compile_rows` for what is currently supported. + let gen_default = match &src_typ { + Type::Bool => { + // There are only two possible values. Repeating one of them results in a warning, + // but let's allow it just so we cover that case, since it's not an error. + for _ in 0..num_cases { + let constructor = u.choose_iter([Constructor::True, Constructor::False])?; + let (branch, branch_dyn) = self.gen_expr(u, typ, max_depth, Flags::TOP)?; + is_dyn |= branch_dyn; + let case = MatchCase { constructor, arguments: Vec::new(), branch }; + match_expr.cases.push(case); + } + + // If we have a non-exhaustive match we have to have a default; otherwise it's optional. + #[allow(clippy::mutable_key_type)] + let cs = + match_expr.cases.iter().map(|c| c.constructor.clone()).collect::>(); + + if cs.len() < 2 { true } else { bool::arbitrary(u)? } + } + Type::Field | Type::Integer(_, _) => { + for _ in 0..num_cases { + let constructor = self.gen_num_match_constructor(u, &src_typ)?; + let (branch, branch_dyn) = self.gen_expr(u, typ, max_depth, Flags::TOP)?; + is_dyn |= branch_dyn; + let case = MatchCase { constructor, arguments: Vec::new(), branch }; + match_expr.cases.push(case); + } + // We won't have an exhaustive match with random integers, so we need a default. + true + } + Type::Tuple(item_types) => { + // There is only one case in the AST that we can generate, which is to unpack the tuple + // into its constituent fields. The compiler would do this, and then generate further + // matches on individual fields. We don't do that here, just make the fields available. + let constructor = Constructor::Tuple(vecmap(item_types, types::to_hir_type)); + let mut arguments = Vec::new(); + self.locals.enter(); + for item_type in item_types { + let item_id = self.next_local_id(); + let item_name = format!("item_{}", local_name(item_id)); + self.locals.add(item_id, false, item_name.clone(), item_type.clone()); + arguments.push((item_id, item_name)); + } + // Generate the original expression we wanted with the new arguments in scope. + let (branch, branch_dyn) = self.gen_expr(u, typ, max_depth, Flags::TOP)?; + is_dyn |= branch_dyn; + let case = MatchCase { constructor, arguments, branch }; + match_expr.cases.push(case); + self.locals.exit(); + // We must not generate a default, or the compiler will panic. + false + } + other => { + unreachable!("unexpected type to generate match for: ${other}"); + } + }; + + // Optionally generate a default case. + if gen_default { + let (default_expr, default_dyn) = self.gen_expr(u, typ, max_depth, Flags::TOP)?; + is_dyn |= default_dyn; + match_expr.default_case = Some(Box::new(default_expr)); + } + + let match_expr = Expression::Match(match_expr); + let expr = if let Some(src_expr) = src_expr { + Expression::Block(vec![src_expr, match_expr]) + } else { + match_expr + }; + + Ok(Some((expr, is_dyn))) + } + + /// Generate a random field that can be used in the match constructor of a numeric type. + fn gen_num_field( + &mut self, + u: &mut Unstructured, + typ: &Type, + ) -> arbitrary::Result { + let literal = self.gen_literal(u, typ)?; + let Expression::Literal(Literal::Integer(field, _, _)) = literal else { + unreachable!("expected Literal::Integer; got {literal:?}"); + }; + Ok(field) + } + + /// Generate a match constructor for a numeric type. + fn gen_num_match_constructor( + &mut self, + u: &mut Unstructured, + typ: &Type, + ) -> arbitrary::Result { + // TODO: Currently the parser does not seem to support the `Constructor::Range` syntax. + // When it does, we should generate either a field, or a range. + let constructor = Constructor::Int(self.gen_num_field(u, typ)?); + Ok(constructor) + } + /// If this is main, and we could have made a call to another function, but we didn't, /// ensure we do, so as not to let all the others we generate go to waste. fn gen_guaranteed_call_from_main( @@ -1813,6 +1997,25 @@ impl<'a> FunctionContext<'a> { let ref_expr = expr::ref_mut(Expression::Ident(let_ident), typ); Expression::Block(vec![let_expr, ref_expr]) } + + /// Create a local let binding over a global variable. + /// + /// Returns the local ID and the `Let` expression. + fn indirect_global( + &mut self, + id: GlobalId, + mutable: bool, + add_to_scope: bool, + ) -> (LocalId, String, Expression) { + let (_, name, typ) = self.globals.get_variable(&id).clone(); + let ident_id = self.next_ident_id(); + let ident = expr::ident(VariableId::Global(id), ident_id, false, name, typ.clone()); + let let_expr = self.let_var(mutable, typ, ident, add_to_scope, false, local_name); + let Expression::Let(Let { id, name, .. }) = &let_expr else { + unreachable!("expected Let; got {let_expr:?}"); + }; + (*id, name.clone(), let_expr) + } } #[cfg(test)] diff --git a/tooling/ast_fuzzer/src/program/rewrite/mod.rs b/tooling/ast_fuzzer/src/program/rewrite/mod.rs index a17626f5157..1efdccdf3a5 100644 --- a/tooling/ast_fuzzer/src/program/rewrite/mod.rs +++ b/tooling/ast_fuzzer/src/program/rewrite/mod.rs @@ -1,4 +1,6 @@ -use noirc_frontend::monomorphization::ast::{Call, Expression, Function, Ident, Program, Type}; +use noirc_frontend::monomorphization::ast::{ + Call, Expression, Function, Ident, LocalId, Program, Type, +}; use super::{ expr, types, @@ -13,23 +15,34 @@ pub(crate) use unreachable::remove_unreachable_functions; /// Find the next local ID and ident IDs (in that order) that we can use to add /// variables to a [Function] during mutations. +/// +/// A more sophisticated alternative would be to return metadata along with the `Program` +/// that contains these values, so we don't have to traverse the AST again, and keep +/// the logic of what introduces new IDs in sync. pub fn next_local_and_ident_id(func: &Function) -> (u32, u32) { let mut next_local_id = func.parameters.iter().map(|p| p.0.0 + 1).max().unwrap_or_default(); let mut next_ident_id = 0; + let mut acc_local_id = |id: LocalId| { + next_local_id = next_local_id.max(id.0 + 1); + }; + visit_expr(&func.body, &mut |expr| { - let local_id = match expr { - Expression::Let(let_) => Some(let_.id), - Expression::For(for_) => Some(for_.index_variable), + match expr { Expression::Ident(ident) => { next_ident_id = next_ident_id.max(ident.id.0 + 1); - None } - _ => None, + Expression::Let(let_) => acc_local_id(let_.id), + Expression::For(for_) => acc_local_id(for_.index_variable), + Expression::Match(match_) => { + for case in &match_.cases { + for (id, _) in &case.arguments { + acc_local_id(*id); + } + } + } + _ => {} }; - if let Some(id) = local_id { - next_local_id = next_local_id.max(id.0 + 1); - } true }); (next_local_id, next_ident_id) diff --git a/tooling/ast_fuzzer/src/program/types.rs b/tooling/ast_fuzzer/src/program/types.rs index 459cb257eee..2a5c8b582dc 100644 --- a/tooling/ast_fuzzer/src/program/types.rs +++ b/tooling/ast_fuzzer/src/program/types.rs @@ -52,6 +52,11 @@ pub fn can_be_main(typ: &Type) -> bool { } } +/// Check if a variable with a given type can be used in a match. +pub fn can_be_matched(typ: &Type) -> bool { + matches!(typ, Type::Unit | Type::Bool | Type::Field | Type::Integer(_, _) | Type::Tuple(_)) +} + /// Collect all the sub-types produced by a type. /// /// It's like a _power set_ of the type. diff --git a/tooling/ast_fuzzer/tests/calibration.rs b/tooling/ast_fuzzer/tests/calibration.rs index 58d3c363c94..71f24c3949e 100644 --- a/tooling/ast_fuzzer/tests/calibration.rs +++ b/tooling/ast_fuzzer/tests/calibration.rs @@ -110,7 +110,7 @@ fn classify(expr: &Expression) -> Option<(&'static str, &'static str)> { Expression::Loop(_) => ("stmt", "loop"), Expression::While(_) => ("stmt", "while"), Expression::If(x) => (if x.typ == Type::Unit { "stmt" } else { "expr" }, "if"), - Expression::Match(_) => todo!("match"), + Expression::Match(x) => (if x.typ == Type::Unit { "stmt" } else { "expr" }, "match"), Expression::Call(x) => (if x.return_type == Type::Unit { "stmt" } else { "expr" }, "call"), Expression::Let(_) => ("stmt", "let"), Expression::Constrain(_, _, _) => ("stmt", "constrain"), diff --git a/tooling/ast_fuzzer/tests/mono.rs b/tooling/ast_fuzzer/tests/mono.rs index 4a0322e1445..14ce78e2ad9 100644 --- a/tooling/ast_fuzzer/tests/mono.rs +++ b/tooling/ast_fuzzer/tests/mono.rs @@ -14,6 +14,7 @@ use noir_ast_fuzzer::{Config, DisplayAstAsNoir, arb_program}; use noirc_driver::{CompileOptions, file_manager_with_stdlib, prepare_crate}; use noirc_errors::CustomDiagnostic; use noirc_frontend::{ + elaborator::UnstableFeature, hir::Context, monomorphization::{ast::Program, monomorphize}, }; @@ -38,6 +39,10 @@ fn arb_ast_roundtrip() { avoid_negative_int_literals: true, // Large ints are rejected in for loops, unless we use suffixes. avoid_large_int_literals: true, + // The compiler introduces "internal variable" even if it's not needed, + // and also rationalizes removes branches that can never be matched, + // (like repeated patterns, superfluous defaults). For now ignore these. + avoid_match: true, // The formatting of `unsafe { ` becomes `{ unsafe {` with extra line breaks. // Let's stick to just Brillig so there is no need for `unsafe` at all. force_brillig: true, @@ -77,7 +82,10 @@ fn monomorphize_snippet(source: String) -> Result context.disable_comptime_printing(); - let _ = noirc_driver::check_crate(&mut context, crate_id, &CompileOptions::default())?; + let options = + CompileOptions { unstable_features: vec![UnstableFeature::Enums], ..Default::default() }; + + let _ = noirc_driver::check_crate(&mut context, crate_id, &options)?; let main_id = context.get_main_function(&crate_id).expect("get_main_function");