From 07328eb5f4fc6b4cd6037fc97f91ddc32de64625 Mon Sep 17 00:00:00 2001 From: Cam Swords Date: Wed, 12 Jun 2024 20:07:30 -0700 Subject: [PATCH] [move][move-2024][ide] Split up match in the compiler (#18179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This make the following changes: 1. It moves some custom query information that was being maintained as part of the typing context (and before that, as part of the HLIR context) onto `ProgramInfo` to allow matching operations to query information about the structure of datatypes for any program information. 2. It splits the main `PatternMatrix` definitions (data + code) into `shared/matching.rs`, along with a `MatchContext` trait that the matrix specialization operations are now generic over. 3. It removes the actual match compilation code from typing, partially reverting #17559 -- counterexample generation and IDE suggestion analysis happens in typing as a visitor over function bodies, but matches remain intact through all of typing now. 4. It reinstates the previous code for compiling `match` as part of HLIR lowering. Along the way, this code also cleaned up a bit of how struct and enum information was being indexed in HLIR lowering, opting to reuse the now-stored TypedProgramInfo (🎉 #17787) as opposed to recomputing this information from scratch. ## Test plan All tests should still work as expected with no other changes. --- ## Release notes Check each box that your changes affect. If none of the boxes relate to your changes, release notes aren't required. For each box you select, include information after the relevant heading that describes the impact of your changes that a user might notice and any actions they must take to implement updates. - [ ] Protocol: - [ ] Nodes (Validators and Full nodes): - [ ] Indexer: - [ ] JSON-RPC: - [ ] GraphQL: - [ ] CLI: - [ ] Rust SDK: --- .../src/hlir/detect_dead_code.rs | 74 +- .../src/hlir/match_compilation.rs | 1307 +++++++++++++++++ .../move/crates/move-compiler/src/hlir/mod.rs | 1 + .../move-compiler/src/hlir/translate.rs | 260 ++-- .../move-compiler/src/shared/matching.rs | 992 +++++++++++++ .../crates/move-compiler/src/shared/mod.rs | 1 + .../move-compiler/src/shared/program_info.rs | 135 +- .../crates/move-compiler/src/typing/core.rs | 217 +-- .../src/typing/dependency_ordering.rs | 55 +- .../src/typing/match_analysis.rs | 778 ++++++++++ .../crates/move-compiler/src/typing/mod.rs | 2 +- .../move-compiler/src/typing/translate.rs | 4 +- 12 files changed, 3418 insertions(+), 408 deletions(-) create mode 100644 external-crates/move/crates/move-compiler/src/hlir/match_compilation.rs create mode 100644 external-crates/move/crates/move-compiler/src/shared/matching.rs create mode 100644 external-crates/move/crates/move-compiler/src/typing/match_analysis.rs diff --git a/external-crates/move/crates/move-compiler/src/hlir/detect_dead_code.rs b/external-crates/move/crates/move-compiler/src/hlir/detect_dead_code.rs index b2a74124ccc41..df536f57c9a7a 100644 --- a/external-crates/move/crates/move-compiler/src/hlir/detect_dead_code.rs +++ b/external-crates/move/crates/move-compiler/src/hlir/detect_dead_code.rs @@ -323,20 +323,22 @@ fn tail(context: &mut Context, e: &T::Exp) -> Option { _ => None, } } - E::Match(_subject, _arms) => { - context - .env - .add_diag(ice!((*eloc, "Found match in detect_dead_code"))); - None - } - E::VariantMatch(subject, _, arms) => { + E::Match(subject, arms) => { if let Some(test_control_flow) = value(context, subject) { context.report_value_error(test_control_flow); return None; }; let arm_somes = arms + .value .iter() - .map(|(_, arm)| tail(context, arm)) + .map(|sp!(_, arm)| { + arm.guard + .as_ref() + .and_then(|guard| value(context, guard)) + .iter() + .for_each(|flow| context.report_value_error(*flow)); + tail(context, &arm.rhs) + }) .collect::>(); if arm_somes.iter().all(|arm_opt| arm_opt.is_some()) { for arm_opt in arm_somes { @@ -346,6 +348,12 @@ fn tail(context: &mut Context, e: &T::Exp) -> Option { } None } + E::VariantMatch(..) => { + context + .env + .add_diag(ice!((*eloc, "Found variant match in detect_dead_code"))); + None + } // Whiles and loops Loops are currently moved to statement position E::While(_, _, _) | E::Loop { .. } => statement(context, e), @@ -444,20 +452,22 @@ fn value(context: &mut Context, e: &T::Exp) -> Option { } None } - E::Match(_subject, _arms) => { - context - .env - .add_diag(ice!((*eloc, "Found match in detect_dead_code"))); - None - } - E::VariantMatch(subject, _, arms) => { + E::Match(subject, arms) => { if let Some(test_control_flow) = value(context, subject) { context.report_value_error(test_control_flow); return None; }; let arm_somes = arms + .value .iter() - .map(|(_, arm)| value(context, arm)) + .map(|sp!(_, arm)| { + arm.guard + .as_ref() + .and_then(|guard| value(context, guard)) + .iter() + .for_each(|flow| context.report_value_error(*flow)); + value(context, &arm.rhs) + }) .collect::>(); if arm_somes.iter().all(|arm_opt| arm_opt.is_some()) { for arm_opt in arm_somes { @@ -467,6 +477,12 @@ fn value(context: &mut Context, e: &T::Exp) -> Option { } None } + E::VariantMatch(_subject, _, _arms) => { + context + .env + .add_diag(ice!((*eloc, "Found variant match in detect_dead_code"))); + None + } E::While(..) | E::Loop { .. } => statement(context, e), E::NamedBlock(name, (_, seq)) => { // a named block in value position checks if the body exits that block; if so, at least @@ -614,25 +630,25 @@ fn statement(context: &mut Context, e: &T::Exp) -> Option { } } } - E::Match(_subject, _arms) => { - context - .env - .add_diag(ice!((*eloc, "Found match in detect_dead_code"))); - None - } - E::VariantMatch(subject, _, arms) => { + E::Match(subject, arms) => { if let Some(test_control_flow) = value(context, subject) { context.report_value_error(test_control_flow); - for (_, arm) in arms.iter() { - statement(context, arm); + for sp!(_, arm) in arms.value.iter() { + arm.guard + .as_ref() + .and_then(|guard| value(context, guard)) + .iter() + .for_each(|flow| context.report_value_error(*flow)); + statement(context, &arm.rhs); } already_reported(*eloc) } else { // if the test was okay but all arms both diverged, we need to report that for the // purpose of trailing semicolons. let arm_somes = arms + .value .iter() - .map(|(_, arm)| statement(context, arm)) + .map(|sp!(_, arm)| statement(context, &arm.rhs)) .collect::>(); if arm_somes.iter().all(|arm_opt| arm_opt.is_some()) { divergent(*eloc) @@ -641,6 +657,12 @@ fn statement(context: &mut Context, e: &T::Exp) -> Option { } } } + E::VariantMatch(_subject, _, _arms) => { + context + .env + .add_diag(ice!((*eloc, "Found variant match in detect_dead_code"))); + None + } E::While(_, test, body) => { if let Some(test_control_flow) = value(context, test) { context.report_value_error(test_control_flow); diff --git a/external-crates/move/crates/move-compiler/src/hlir/match_compilation.rs b/external-crates/move/crates/move-compiler/src/hlir/match_compilation.rs new file mode 100644 index 0000000000000..3b73a1d2de3ac --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/hlir/match_compilation.rs @@ -0,0 +1,1307 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + expansion::ast::{Fields, ModuleIdent, Mutability, Value, Value_}, + hlir::translate::Context, + ice, ice_assert, + naming::ast::{self as N, BuiltinTypeName_, Type, UseFuns, Var}, + parser::ast::{DatatypeName, Field, VariantName}, + shared::{ + ast_debug::{AstDebug, AstWriter}, + matching::*, + string_utils::debug_print, + unique_map::UniqueMap, + }, + typing::ast::{self as T, MatchPattern, UnannotatedPat_ as TP}, +}; +use move_ir_types::location::*; +use move_proc_macros::growing_stack; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; + +//************************************************************************************************** +// Match Compilation +//************************************************************************************************** +// This mostly follows the classical Maranget (2008) implementation toward optimal decision trees. + +type Fringe = VecDeque; + +#[derive(Clone)] +enum StructUnpack { + Default(T), + Unpack(Vec<(Field, Var, Type)>, T), +} + +enum MatchStep { + Leaf(Vec), + Failure, + LiteralSwitch { + subject: FringeEntry, + subject_binders: Vec<(Mutability, Var)>, + fringe: Fringe, + arms: BTreeMap, + default: PatternMatrix, + }, + StructUnpack { + subject: FringeEntry, + subject_binders: Vec<(Mutability, Var)>, + tyargs: Vec, + unpack: StructUnpack<(Fringe, PatternMatrix)>, + }, + VariantSwitch { + subject: FringeEntry, + subject_binders: Vec<(Mutability, Var)>, + tyargs: Vec, + arms: BTreeMap, Fringe, PatternMatrix)>, + default: (Fringe, PatternMatrix), + }, +} + +#[derive(Clone)] +enum WorkResult { + Leaf(Vec), + Failure, + LiteralSwitch { + subject: FringeEntry, + subject_binders: Vec<(Mutability, Var)>, + arms: BTreeMap, + default: usize, // default + }, + StructUnpack { + subject: FringeEntry, + subject_binders: Vec<(Mutability, Var)>, + tyargs: Vec, + unpack: StructUnpack, + }, + VariantSwitch { + subject: FringeEntry, + subject_binders: Vec<(Mutability, Var)>, + tyargs: Vec, + arms: BTreeMap, usize)>, + default: usize, + }, +} + +pub(super) fn compile_match( + context: &mut Context, + result_type: &Type, + subject: T::Exp, + arms: Spanned>, +) -> T::Exp { + let arms_loc = arms.loc; + // NB: `from` also flattens `or` and converts constants into guards. + let (pattern_matrix, arms) = PatternMatrix::from(context, subject.ty.clone(), arms.value); + + let mut compilation_results: BTreeMap = BTreeMap::new(); + + let (mut initial_binders, init_subject, match_subject) = { + let subject_var = context.new_match_var("unpack_subject".to_string(), arms_loc); + let subject_loc = subject.exp.loc; + let match_var = context.new_match_var("match_subject".to_string(), arms_loc); + + let subject_entry = FringeEntry { + var: subject_var, + ty: subject.ty.clone(), + }; + let subject_borrow_rhs = make_var_ref(subject_entry.clone()); + + let match_entry = FringeEntry { + var: match_var, + ty: subject_borrow_rhs.ty.clone(), + }; + + let subject_binder = { + let lhs_loc = subject_loc; + let lhs_lvalue = make_lvalue(subject_var, Mutability::Imm, subject.ty.clone()); + let binder = T::SequenceItem_::Bind( + sp(lhs_loc, vec![lhs_lvalue]), + vec![Some(subject.ty.clone())], + Box::new(subject), + ); + sp(lhs_loc, binder) + }; + + let subject_borrow = { + let lhs_loc = arms_loc; + let lhs_lvalue = make_lvalue(match_var, Mutability::Imm, subject_borrow_rhs.ty.clone()); + let binder = T::SequenceItem_::Bind( + sp(lhs_loc, vec![lhs_lvalue]), + vec![Some(subject_borrow_rhs.ty.clone())], + subject_borrow_rhs, + ); + sp(lhs_loc, binder) + }; + + ( + VecDeque::from([subject_binder, subject_borrow]), + subject_entry, + match_entry, + ) + }; + + let mut work_queue: Vec<(usize, Fringe, PatternMatrix)> = + vec![(0, VecDeque::from([match_subject]), pattern_matrix)]; + + let mut work_id = 0; + + let mut next_id = || { + work_id += 1; + work_id + }; + + while let Some((cur_id, init_fringe, matrix)) = work_queue.pop() { + debug_print!( + context.debug.match_work_queue, + ("work queue entry" => cur_id; fmt), + (lines "fringe" => &init_fringe; sdbg), + ("matrix" => matrix; verbose) + ); + let redefined: Option = + match compile_match_head(context, init_fringe.clone(), matrix) { + MatchStep::Leaf(leaf) => compilation_results.insert(cur_id, WorkResult::Leaf(leaf)), + MatchStep::Failure => compilation_results.insert(cur_id, WorkResult::Failure), + MatchStep::LiteralSwitch { + subject, + subject_binders, + fringe, + arms, + default, + } => { + let mut answer_map = BTreeMap::new(); + for (value, matrix) in arms { + let work_id = next_id(); + answer_map.insert(value, work_id); + work_queue.push((work_id, fringe.clone(), matrix)); + } + let default_work_id = next_id(); + work_queue.push((default_work_id, fringe, default)); + let result = WorkResult::LiteralSwitch { + subject, + subject_binders, + arms: answer_map, + default: default_work_id, + }; + compilation_results.insert(cur_id, result) + } + MatchStep::StructUnpack { + subject, + subject_binders, + tyargs, + unpack, + } => { + let unpack_work_id = next_id(); + let unpack = match unpack { + StructUnpack::Default((fringe, matrix)) => { + work_queue.push((unpack_work_id, fringe, matrix)); + StructUnpack::Default(unpack_work_id) + } + StructUnpack::Unpack(dtor_fields, (fringe, matrix)) => { + work_queue.push((unpack_work_id, fringe, matrix)); + StructUnpack::Unpack(dtor_fields, unpack_work_id) + } + }; + compilation_results.insert( + cur_id, + WorkResult::StructUnpack { + subject, + subject_binders, + tyargs, + unpack, + }, + ) + } + + MatchStep::VariantSwitch { + subject, + subject_binders, + tyargs, + arms, + default: (dfringe, dmatrix), + } => { + let mut answer_map = BTreeMap::new(); + for (name, (dtor_fields, fringe, matrix)) in arms { + let work_id = next_id(); + answer_map.insert(name, (dtor_fields, work_id)); + work_queue.push((work_id, fringe, matrix)); + } + let default_work_id = next_id(); + work_queue.push((default_work_id, dfringe, dmatrix)); + compilation_results.insert( + cur_id, + WorkResult::VariantSwitch { + subject, + subject_binders, + tyargs, + arms: answer_map, + default: default_work_id, + }, + ) + } + }; + ice_assert!( + context.env, + redefined.is_none(), + arms_loc, + "Match work queue went awry" + ); + } + + let match_start = compilation_results.remove(&0).unwrap(); + let mut resolution_context = ResolutionContext { + hlir_context: context, + output_type: result_type, + arms: &arms, + arms_loc, + results: &mut compilation_results, + }; + let match_exp = resolve_result(&mut resolution_context, &init_subject, match_start); + + let eloc = match_exp.exp.loc; + let mut seq = VecDeque::new(); + seq.append(&mut initial_binders); + seq.push_back(sp(eloc, T::SequenceItem_::Seq(Box::new(match_exp)))); + let exp_value = sp(eloc, T::UnannotatedExp_::Block((UseFuns::new(0), seq))); + T::exp(result_type.clone(), exp_value) +} + +fn compile_match_head( + context: &mut Context, + mut fringe: VecDeque, + mut matrix: PatternMatrix, +) -> MatchStep { + debug_print!( + context.debug.match_specialization, + ("-----\ncompiling with fringe queue entry" => fringe; dbg) + ); + if matrix.is_empty() { + MatchStep::Failure + } else if let Some(leaf) = matrix.wild_arm_opt(&fringe) { + MatchStep::Leaf(leaf) + } else if fringe[0].ty.value.unfold_to_builtin_type_name().is_some() { + let subject = fringe + .pop_front() + .expect("ICE empty fringe in match compilation"); + let mut subject_binders = vec![]; + // treat column as a literal + let lits = matrix.first_lits(); + let mut arms = BTreeMap::new(); + for lit in lits { + let lit_loc = lit.loc; + debug_print!(context.debug.match_specialization, ("lit specializing" => lit ; fmt)); + let (mut new_binders, inner_matrix) = matrix.specialize_literal(&lit); + debug_print!( + context.debug.match_specialization, + ("binders" => &new_binders; dbg), ("specialized" => inner_matrix) + ); + subject_binders.append(&mut new_binders); + ice_assert!( + context.env, + arms.insert(lit, inner_matrix).is_none(), + lit_loc, + "Specialization failed" + ); + } + let (mut new_binders, default) = matrix.specialize_default(); + debug_print!(context.debug.match_specialization, ("default binders" => &new_binders; dbg)); + subject_binders.append(&mut new_binders); + MatchStep::LiteralSwitch { + subject, + subject_binders, + fringe, + arms, + default, + } + } else { + let subject = fringe + .pop_front() + .expect("ICE empty fringe in match compilation"); + let tyargs = subject.ty.value.type_arguments().unwrap().clone(); + let mut subject_binders = vec![]; + debug_print!( + context.debug.match_specialization, + ("subject" => subject), + ("matrix" => matrix) + ); + let (mident, datatype_name) = subject + .ty + .value + .unfold_to_type_name() + .and_then(|sp!(_, name)| name.datatype_name()) + .expect("ICE non-datatype type in head constructor fringe position"); + + if context.info.is_struct(&mident, &datatype_name) { + // If we have an actual destructuring anywhere, we do that and take the specialized + // matrix (which holds the default matrix and bindings, for our purpose). If we don't, + // we just take the default matrix. + let unpack = if let Some((ploc, arg_types)) = matrix.first_struct_ctors() { + let fringe_binders = context.make_imm_ref_match_binders(ploc, arg_types); + let fringe_exps = make_fringe_entries(&fringe_binders); + let mut inner_fringe = fringe.clone(); + for fringe_exp in fringe_exps.into_iter().rev() { + inner_fringe.push_front(fringe_exp); + } + let bind_tys = fringe_binders + .iter() + .map(|(_, _, ty)| ty) + .collect::>(); + debug_print!( + context.debug.match_specialization, ("struct specialized" => datatype_name; dbg) + ); + let (mut new_binders, inner_matrix) = matrix.specialize_struct(context, bind_tys); + debug_print!(context.debug.match_specialization, + ("binders" => new_binders; dbg), + ("specialized" => inner_matrix)); + subject_binders.append(&mut new_binders); + StructUnpack::Unpack(fringe_binders, (inner_fringe, inner_matrix)) + } else { + let (mut new_binders, default_matrix) = matrix.specialize_default(); + subject_binders.append(&mut new_binders); + StructUnpack::Default((fringe, default_matrix)) + }; + MatchStep::StructUnpack { + subject, + subject_binders, + tyargs, + unpack, + } + } else { + let mut unmatched_variants = context + .info + .enum_variants(&mident, &datatype_name) + .into_iter() + .collect::>(); + + let ctors = matrix.first_variant_ctors(); + + let mut arms = BTreeMap::new(); + for (ctor, (ploc, arg_types)) in ctors { + unmatched_variants.remove(&ctor); + let fringe_binders = context.make_imm_ref_match_binders(ploc, arg_types); + let fringe_exps = make_fringe_entries(&fringe_binders); + let mut inner_fringe = fringe.clone(); + for fringe_exp in fringe_exps.into_iter().rev() { + inner_fringe.push_front(fringe_exp); + } + let bind_tys = fringe_binders + .iter() + .map(|(_, _, ty)| ty) + .collect::>(); + debug_print!( + context.debug.match_specialization, ("enum specialized" => datatype_name; dbg) + ); + let (mut new_binders, inner_matrix) = + matrix.specialize_variant(context, &ctor, bind_tys); + debug_print!(context.debug.match_specialization, + ("binders" => new_binders; dbg), + ("specialized" => inner_matrix)); + subject_binders.append(&mut new_binders); + ice_assert!( + context.env, + arms.insert(ctor, (fringe_binders, inner_fringe, inner_matrix)) + .is_none(), + ploc, + "Inserted duplicate ctor" + ); + } + + let (mut new_binders, default_matrix) = matrix.specialize_default(); + subject_binders.append(&mut new_binders); + + MatchStep::VariantSwitch { + subject, + subject_binders, + tyargs, + arms, + default: (fringe, default_matrix), + } + } + } +} + +fn make_fringe_entries(binders: &[(Field, Var, Type)]) -> VecDeque { + binders + .iter() + .map(|(_, var, ty)| FringeEntry { + var: *var, + ty: ty.clone(), + }) + .collect::>() +} + +//------------------------------------------------ +// Result Construction +//------------------------------------------------ + +struct ResolutionContext<'ctxt, 'call> { + hlir_context: &'call mut Context<'ctxt>, + output_type: &'call Type, + arms: &'call Vec, + arms_loc: Loc, + results: &'call mut BTreeMap, +} + +impl<'ctxt, 'call> ResolutionContext<'ctxt, 'call> { + fn arm(&self, index: usize) -> T::Exp { + self.arms[index].clone() + } + + fn arms_loc(&self) -> Loc { + self.arms_loc + } + + fn work_result(&mut self, work_id: usize) -> WorkResult { + self.results.remove(&work_id).unwrap() + } + + fn copy_work_result(&mut self, work_id: usize) -> WorkResult { + self.results.get(&work_id).unwrap().clone() + } + + fn output_type(&self) -> Type { + self.output_type.clone() + } +} + +#[growing_stack] +fn resolve_result( + context: &mut ResolutionContext, + init_subject: &FringeEntry, + result: WorkResult, +) -> T::Exp { + match result { + WorkResult::Leaf(leaf) => make_leaf(context, init_subject, leaf), + WorkResult::Failure => T::exp( + context.output_type(), + sp(context.arms_loc, T::UnannotatedExp_::UnresolvedError), + ), + WorkResult::VariantSwitch { + subject, + subject_binders, + tyargs, + mut arms, + default: default_ndx, + } => { + let (m, e) = subject + .ty + .value + .unfold_to_type_name() + .and_then(|sp!(_, name)| name.datatype_name()) + .unwrap(); + let bindings = subject_binders + .into_iter() + .map(|(mut_, binder)| (binder, (mut_, subject.clone()))) + .collect(); + + let sorted_variants: Vec = context.hlir_context.info.enum_variants(&m, &e); + let mut blocks = vec![]; + for v in sorted_variants { + if let Some((unpack_fields, result_ndx)) = arms.remove(&v) { + let work_result = context.work_result(result_ndx); + let rest_result = resolve_result(context, init_subject, work_result); + let unpack_block = make_match_variant_unpack( + m, + e, + v, + tyargs.clone(), + unpack_fields, + subject.clone(), + rest_result, + ); + blocks.push((v, unpack_block)); + } else { + let work_result = context.copy_work_result(default_ndx); + let rest_result = resolve_result(context, init_subject, work_result); + blocks.push((v, rest_result)); + } + } + let out_exp = T::UnannotatedExp_::VariantMatch(make_var_ref(subject), (m, e), blocks); + let body_exp = T::exp(context.output_type(), sp(context.arms_loc(), out_exp)); + make_copy_bindings(bindings, body_exp) + } + WorkResult::StructUnpack { + subject, + subject_binders, + tyargs, + unpack, + } => { + let (m, s) = subject + .ty + .value + .unfold_to_type_name() + .and_then(|sp!(_, name)| name.datatype_name()) + .unwrap(); + let bindings = subject_binders + .into_iter() + .map(|(mut_, binder)| (binder, (mut_, subject.clone()))) + .collect(); + let unpack_exp = match unpack { + StructUnpack::Default(result_ndx) => { + let work_result = context.work_result(result_ndx); + resolve_result(context, init_subject, work_result) + } + StructUnpack::Unpack(unpack_fields, result_ndx) => { + let work_result = context.work_result(result_ndx); + let rest_result = resolve_result(context, init_subject, work_result); + make_match_struct_unpack( + m, + s, + tyargs.clone(), + unpack_fields, + subject.clone(), + rest_result, + ) + } + }; + make_copy_bindings(bindings, unpack_exp) + } + WorkResult::LiteralSwitch { + subject, + subject_binders, + mut arms, + default: _, + } if matches!( + subject.ty.value.unfold_to_builtin_type_name(), + Some(sp!(_, BuiltinTypeName_::Bool)) + ) && arms.len() == 2 => + { + let bindings = subject_binders + .into_iter() + .map(|(mut_, binder)| (binder, (mut_, subject.clone()))) + .collect(); + // If the literal switch for a boolean is saturated, no default case. + let lit_subject = make_match_lit(subject.clone()); + let true_arm_ndx = arms + .remove(&sp(Loc::invalid(), Value_::Bool(true))) + .unwrap(); + let false_arm_ndx = arms + .remove(&sp(Loc::invalid(), Value_::Bool(false))) + .unwrap(); + + let true_arm_result = context.work_result(true_arm_ndx); + let false_arm_result = context.work_result(false_arm_ndx); + + let true_arm = resolve_result(context, init_subject, true_arm_result); + let false_arm = resolve_result(context, init_subject, false_arm_result); + let result_type = true_arm.ty.clone(); + + make_copy_bindings( + bindings, + make_if_else(lit_subject, true_arm, false_arm, result_type), + ) + } + WorkResult::LiteralSwitch { + subject, + subject_binders, + arms: map, + default, + } => { + let bindings = subject_binders + .into_iter() + .map(|(mut_, binder)| (binder, (mut_, subject.clone()))) + .collect(); + let lit_subject = make_match_lit(subject.clone()); + + let mut entries = map.into_iter().collect::>(); + entries.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); + + let else_work_result = context.work_result(default); + let mut out_exp = resolve_result(context, init_subject, else_work_result); + + for (key, result_ndx) in entries.into_iter().rev() { + let work_result = context.work_result(result_ndx); + let match_arm = resolve_result(context, init_subject, work_result); + let test_exp = make_lit_test(lit_subject.clone(), key); + let result_ty = out_exp.ty.clone(); + out_exp = make_if_else(test_exp, match_arm, out_exp, result_ty); + } + make_copy_bindings(bindings, out_exp) + } + } +} + +fn make_leaf( + context: &mut ResolutionContext, + subject: &FringeEntry, + mut leaf: Vec, +) -> T::Exp { + assert!(!leaf.is_empty(), "ICE empty leaf in matching"); + + if leaf.len() == 1 { + let last = leaf.pop().unwrap(); + ice_assert!( + context.hlir_context.env, + last.guard.is_none(), + last.guard.unwrap().exp.loc, + "Must have a non-guarded leaf" + ); + return make_copy_bindings(last.bindings, make_arm(context, subject.clone(), last.arm)); + } + + let last = leaf.pop().unwrap(); + ice_assert!( + context.hlir_context.env, + last.guard.is_none(), + last.guard.unwrap().exp.loc, + "Must have a non-guarded leaf" + ); + let mut out_exp = + make_copy_bindings(last.bindings, make_arm(context, subject.clone(), last.arm)); + let out_ty = out_exp.ty.clone(); + while let Some(arm) = leaf.pop() { + ice_assert!( + context.hlir_context.env, + arm.guard.is_some(), + arm.loc, + "Expected a guard" + ); + out_exp = make_guard_exp(context, subject, arm, out_exp, out_ty.clone()); + } + out_exp +} + +fn make_guard_exp( + context: &mut ResolutionContext, + subject: &FringeEntry, + arm: ArmResult, + cur_exp: T::Exp, + result_ty: Type, +) -> T::Exp { + let ArmResult { + loc: _, + bindings, + guard, + arm, + } = arm; + let guard_arm = make_arm(context, subject.clone(), arm); + let body = make_if_else(*guard.unwrap(), guard_arm, cur_exp, result_ty); + make_copy_bindings(bindings, body) +} + +fn make_arm(context: &mut ResolutionContext, subject: FringeEntry, arm: Arm) -> T::Exp { + let arm_exp = context.arm(arm.index); + make_arm_unpack( + context, + subject, + arm.orig_pattern, + &arm.rhs_binders, + arm_exp, + ) +} + +fn make_arm_unpack( + context: &mut ResolutionContext, + subject: FringeEntry, + pattern: MatchPattern, + rhs_binders: &BTreeSet, + next: T::Exp, +) -> T::Exp { + let ploc = pattern.pat.loc; + let mut seq = VecDeque::new(); + + let mut queue: VecDeque<(FringeEntry, MatchPattern)> = VecDeque::from([(subject, pattern)]); + + // TODO(cgswords): we can coalese patterns a bit here, but don't for now. + while let Some((entry, pat)) = queue.pop_front() { + let ploc = pat.pat.loc; + match pat.pat.value { + TP::Variant(m, e, v, tys, fs) => { + let Some((queue_entries, unpack)) = + arm_variant_unpack(context, None, ploc, m, e, tys, v, fs, entry) + else { + context.hlir_context.env.add_diag(ice!(( + ploc, + "Did not build an arm unpack for a value variant" + ))); + continue; + }; + for entry in queue_entries.into_iter().rev() { + queue.push_front(entry); + } + seq.push_back(unpack); + } + TP::BorrowVariant(mut_, m, e, v, tys, fs) => { + let Some((queue_entries, unpack)) = + arm_variant_unpack(context, Some(mut_), ploc, m, e, tys, v, fs, entry) + else { + continue; + }; + for entry in queue_entries.into_iter().rev() { + queue.push_front(entry); + } + seq.push_back(unpack); + } + TP::Struct(m, s, tys, fs) => { + let Some((queue_entries, unpack)) = + arm_struct_unpack(context, None, ploc, m, s, tys, fs, entry) + else { + context.hlir_context.env.add_diag(ice!(( + ploc, + "Did not build an arm unpack for a value struct" + ))); + continue; + }; + for entry in queue_entries.into_iter().rev() { + queue.push_front(entry); + } + seq.push_back(unpack); + } + TP::BorrowStruct(mut_, m, s, tys, fs) => { + let Some((queue_entries, unpack)) = + arm_struct_unpack(context, Some(mut_), ploc, m, s, tys, fs, entry) + else { + continue; + }; + for entry in queue_entries.into_iter().rev() { + queue.push_front(entry); + } + seq.push_back(unpack); + } + TP::Literal(_) => (), + TP::Binder(mut_, x) if rhs_binders.contains(&x) => { + seq.push_back(make_move_binding(x, mut_, entry.ty.clone(), entry)) + } + TP::Binder(_, _) => (), + TP::Wildcard => (), + TP::At(x, inner) => { + // See comment in typing/translate.rs at pattern typing for more information. + let x_in_rhs_binders = rhs_binders.contains(&x); + let inner_has_rhs_binders = match_pattern_has_binders(&inner, rhs_binders); + match (x_in_rhs_binders, inner_has_rhs_binders) { + // make a copy of the value (or ref) and do both sides + (true, true) => { + let bind_entry = entry.clone(); + seq.push_back(make_copy_binding( + x, + Mutability::Imm, + bind_entry.ty.clone(), + bind_entry, + )); + queue.push_front((entry, *inner)); + } + // no unpack needed, just move the value to the x + (true, false) => seq.push_back(make_move_binding( + x, + Mutability::Imm, + entry.ty.clone(), + entry, + )), + // we need to unpack either way, handling wildcards and the like + (false, _) => queue.push_front((entry, *inner)), + } + } + TP::ErrorPat => (), + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + } + } + + let nloc = next.exp.loc; + let out_type = next.ty.clone(); + seq.push_back(sp(nloc, T::SequenceItem_::Seq(Box::new(next)))); + + let body = T::UnannotatedExp_::Block((UseFuns::new(0), seq)); + T::exp(out_type, sp(ploc, body)) +} + +fn match_pattern_has_binders(pat: &T::MatchPattern, rhs_binders: &BTreeSet) -> bool { + match &pat.pat.value { + TP::Binder(_, x) => rhs_binders.contains(x), + TP::At(x, inner) => { + rhs_binders.contains(x) || match_pattern_has_binders(inner, rhs_binders) + } + TP::Variant(_, _, _, _, fields) | TP::BorrowVariant(_, _, _, _, _, fields) => fields + .iter() + .any(|(_, _, (_, (_, pat)))| match_pattern_has_binders(pat, rhs_binders)), + TP::Struct(_, _, _, fields) | TP::BorrowStruct(_, _, _, _, fields) => fields + .iter() + .any(|(_, _, (_, (_, pat)))| match_pattern_has_binders(pat, rhs_binders)), + TP::Literal(_) => false, + TP::Wildcard => false, + TP::ErrorPat => false, + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + } +} + +fn arm_variant_unpack( + context: &mut ResolutionContext, + mut_ref: Option, + pat_loc: Loc, + mident: ModuleIdent, + enum_: DatatypeName, + tyargs: Vec, + variant: VariantName, + fields: Fields<(Type, MatchPattern)>, + rhs: FringeEntry, +) -> Option<(Vec<(FringeEntry, MatchPattern)>, T::SequenceItem)> { + let all_wild = fields + .iter() + .all(|(_, _, (_, (_, pat)))| matches!(pat.pat.value, TP::Wildcard)) + || fields.is_empty(); + // If we are matching a ref with no fields under it, we aren't going to drop so + // we just continue on. + if all_wild && mut_ref.is_some() { + return None; + } + + let (queue_entries, fields) = + make_arm_variant_unpack_fields(context, mut_ref, pat_loc, mident, enum_, variant, fields); + let unpack = make_arm_variant_unpack_stmt(mut_ref, mident, enum_, variant, tyargs, fields, rhs); + Some((queue_entries, unpack)) +} + +fn arm_struct_unpack( + context: &mut ResolutionContext, + mut_ref: Option, + pat_loc: Loc, + mident: ModuleIdent, + struct_: DatatypeName, + tyargs: Vec, + fields: Fields<(Type, MatchPattern)>, + rhs: FringeEntry, +) -> Option<(Vec<(FringeEntry, MatchPattern)>, T::SequenceItem)> { + let all_wild = fields + .iter() + .all(|(_, _, (_, (_, pat)))| matches!(pat.pat.value, TP::Wildcard)) + || fields.is_empty(); + // If we are matching a ref with no fields under it, we aren't going to drop so + // we just continue on. + if all_wild && mut_ref.is_some() { + return None; + } + + let (queue_entries, fields) = + make_arm_struct_unpack_fields(context, mut_ref, pat_loc, mident, struct_, fields); + let unpack = make_arm_struct_unpack_stmt(mut_ref, mident, struct_, tyargs, fields, rhs); + Some((queue_entries, unpack)) +} + +//------------------------------------------------ +// Unpack Field Builders +//------------------------------------------------ + +fn make_arm_variant_unpack_fields( + context: &mut ResolutionContext, + mut_ref: Option, + pat_loc: Loc, + mident: ModuleIdent, + enum_: DatatypeName, + variant: VariantName, + fields: Fields<(Type, MatchPattern)>, +) -> (Vec<(FringeEntry, MatchPattern)>, Vec<(Field, Var, Type)>) { + let field_pats = fields.clone().map(|_key, (ndx, (_, pat))| (ndx, pat)); + + let field_tys = { + let field_tys = fields.map(|_key, (ndx, (ty, _))| (ndx, ty)); + if let Some(mut_) = mut_ref { + field_tys.map(|_field, (ndx, sp!(loc, ty))| { + ( + ndx, + sp(loc, N::Type_::Ref(mut_, Box::new(sp(loc, ty.base_type_())))), + ) + }) + } else { + field_tys + } + }; + let fringe_binders = context.hlir_context.make_unpack_binders(pat_loc, field_tys); + let fringe_exps = make_fringe_entries(&fringe_binders); + + let decl_fields = context + .hlir_context + .info + .enum_variant_fields(&mident, &enum_, &variant); + let ordered_pats = order_fields_by_decl(decl_fields, field_pats); + + let mut unpack_fields: Vec<(Field, Var, Type)> = vec![]; + assert!(fringe_exps.len() == ordered_pats.len()); + for (fringe_exp, (_, field, _)) in fringe_exps.iter().zip(ordered_pats.iter()) { + unpack_fields.push((*field, fringe_exp.var, fringe_exp.ty.clone())); + } + let queue_entries = fringe_exps + .into_iter() + .zip( + ordered_pats + .into_iter() + .map(|(_, _, ordered_pat)| ordered_pat), + ) + .collect::>(); + + (queue_entries, unpack_fields) +} + +fn make_arm_struct_unpack_fields( + context: &mut ResolutionContext, + mut_ref: Option, + pat_loc: Loc, + mident: ModuleIdent, + struct_: DatatypeName, + fields: Fields<(Type, MatchPattern)>, +) -> (Vec<(FringeEntry, MatchPattern)>, Vec<(Field, Var, Type)>) { + let field_pats = fields.clone().map(|_key, (ndx, (_, pat))| (ndx, pat)); + + let field_tys = { + let field_tys = fields.map(|_key, (ndx, (ty, _))| (ndx, ty)); + if let Some(mut_) = mut_ref { + field_tys.map(|_field, (ndx, sp!(loc, ty))| { + ( + ndx, + sp(loc, N::Type_::Ref(mut_, Box::new(sp(loc, ty.base_type_())))), + ) + }) + } else { + field_tys + } + }; + let fringe_binders = context.hlir_context.make_unpack_binders(pat_loc, field_tys); + let fringe_exps = make_fringe_entries(&fringe_binders); + + let decl_fields = context.hlir_context.info.struct_fields(&mident, &struct_); + let ordered_pats = order_fields_by_decl(decl_fields, field_pats); + + let mut unpack_fields: Vec<(Field, Var, Type)> = vec![]; + assert!(fringe_exps.len() == ordered_pats.len()); + for (fringe_exp, (_, field, _)) in fringe_exps.iter().zip(ordered_pats.iter()) { + unpack_fields.push((*field, fringe_exp.var, fringe_exp.ty.clone())); + } + let queue_entries = fringe_exps + .into_iter() + .zip( + ordered_pats + .into_iter() + .map(|(_, _, ordered_pat)| ordered_pat), + ) + .collect::>(); + + (queue_entries, unpack_fields) +} + +//------------------------------------------------ +// Expression Creation Helpers +//------------------------------------------------ + +fn make_var_ref(subject: FringeEntry) -> Box { + let FringeEntry { var, ty } = subject; + match ty { + sp!(_, N::Type_::Ref(false, _)) => { + let loc = var.loc; + Box::new(make_copy_exp(ty, loc, var)) + } + sp!(_, N::Type_::Ref(true, inner)) => { + // NB(cswords): we freeze the mut ref at the non-mut ref type. + let loc = var.loc; + let ref_ty = sp(loc, N::Type_::Ref(true, inner.clone())); + let freeze_arg = make_copy_exp(ref_ty, loc, var); + let freeze_ty = sp(loc, N::Type_::Ref(false, inner)); + Box::new(make_freeze_exp(freeze_ty, loc, freeze_arg)) + } + ty => { + // NB(cswords): we borrow the local + let loc = var.loc; + let ref_ty = sp(loc, N::Type_::Ref(false, Box::new(ty))); + let borrow_exp = T::UnannotatedExp_::BorrowLocal(false, var); + Box::new(T::exp(ref_ty, sp(loc, borrow_exp))) + } + } +} + +// Performs an unpack for the purpose of matching, where we are matching against an imm. ref. +fn make_match_variant_unpack( + mident: ModuleIdent, + enum_: DatatypeName, + variant: VariantName, + tyargs: Vec, + fields: Vec<(Field, Var, Type)>, + rhs: FringeEntry, + next: T::Exp, +) -> T::Exp { + assert!(matches!(rhs.ty.value, N::Type_::Ref(false, _))); + let mut seq = VecDeque::new(); + + let rhs_loc = rhs.var.loc; + let mut lvalue_fields: Fields<(Type, T::LValue)> = UniqueMap::new(); + + for (ndx, (field_name, var, ty)) in fields.into_iter().enumerate() { + assert!(ty.value.is_ref().is_some()); + let var_lvalue = make_lvalue(var, Mutability::Imm, ty.clone()); + let lhs_ty = sp(ty.loc, ty.value.base_type_()); + lvalue_fields + .add(field_name, (ndx, (lhs_ty, var_lvalue))) + .unwrap(); + } + + let unpack_lvalue = sp( + rhs_loc, + T::LValue_::BorrowUnpackVariant(false, mident, enum_, variant, tyargs, lvalue_fields), + ); + + let FringeEntry { var, ty } = rhs; + let rhs = Box::new(make_copy_exp(ty.clone(), var.loc, var)); + let binder = T::SequenceItem_::Bind(sp(rhs_loc, vec![unpack_lvalue]), vec![Some(ty)], rhs); + seq.push_back(sp(rhs_loc, binder)); + + let result_type = next.ty.clone(); + let eloc = next.exp.loc; + seq.push_back(sp(eloc, T::SequenceItem_::Seq(Box::new(next)))); + + let exp_value = sp(eloc, T::UnannotatedExp_::Block((UseFuns::new(0), seq))); + T::exp(result_type, exp_value) +} + +// Performs a struct unpack for the purpose of matching, where we are matching against an imm. ref. +// Note that unpacking refs is a lie; this is +fn make_match_struct_unpack( + mident: ModuleIdent, + struct_: DatatypeName, + tyargs: Vec, + fields: Vec<(Field, Var, Type)>, + rhs: FringeEntry, + next: T::Exp, +) -> T::Exp { + assert!(matches!(rhs.ty.value, N::Type_::Ref(false, _))); + let mut seq = VecDeque::new(); + + let rhs_loc = rhs.var.loc; + let mut lvalue_fields: Fields<(Type, T::LValue)> = UniqueMap::new(); + + for (ndx, (field_name, var, ty)) in fields.into_iter().enumerate() { + assert!(ty.value.is_ref().is_some()); + let var_lvalue = make_lvalue(var, Mutability::Imm, ty.clone()); + let lhs_ty = sp(ty.loc, ty.value.base_type_()); + lvalue_fields + .add(field_name, (ndx, (lhs_ty, var_lvalue))) + .unwrap(); + } + + let unpack_lvalue = sp( + rhs_loc, + T::LValue_::BorrowUnpack(false, mident, struct_, tyargs, lvalue_fields), + ); + + let FringeEntry { var, ty } = rhs; + let rhs = Box::new(make_copy_exp(ty.clone(), var.loc, var)); + let binder = T::SequenceItem_::Bind(sp(rhs_loc, vec![unpack_lvalue]), vec![Some(ty)], rhs); + seq.push_back(sp(rhs_loc, binder)); + + let result_type = next.ty.clone(); + let eloc = next.exp.loc; + seq.push_back(sp(eloc, T::SequenceItem_::Seq(Box::new(next)))); + + let exp_value = sp(eloc, T::UnannotatedExp_::Block((UseFuns::new(0), seq))); + T::exp(result_type, exp_value) +} + +fn make_arm_variant_unpack_stmt( + mut_ref: Option, + mident: ModuleIdent, + enum_: DatatypeName, + variant: VariantName, + tyargs: Vec, + fields: Vec<(Field, Var, Type)>, + rhs: FringeEntry, +) -> T::SequenceItem { + let rhs_loc = rhs.var.loc; + let mut lvalue_fields: Fields<(Type, T::LValue)> = UniqueMap::new(); + + for (ndx, (field_name, var, ty)) in fields.into_iter().enumerate() { + let var_lvalue = make_lvalue(var, Mutability::Imm, ty.clone()); + let lhs_ty = sp(ty.loc, ty.value.base_type_()); + lvalue_fields + .add(field_name, (ndx, (lhs_ty, var_lvalue))) + .unwrap(); + } + + let unpack_lvalue_ = if let Some(mut_) = mut_ref { + T::LValue_::BorrowUnpackVariant(mut_, mident, enum_, variant, tyargs, lvalue_fields) + } else { + T::LValue_::UnpackVariant(mident, enum_, variant, tyargs, lvalue_fields) + }; + let rhs_ty = rhs.ty.clone(); + let rhs: Box = Box::new(rhs.into_move_exp()); + let binder = T::SequenceItem_::Bind( + sp(rhs_loc, vec![sp(rhs_loc, unpack_lvalue_)]), + vec![Some(rhs_ty)], + rhs, + ); + sp(rhs_loc, binder) +} + +fn make_arm_struct_unpack_stmt( + mut_ref: Option, + mident: ModuleIdent, + struct_: DatatypeName, + tyargs: Vec, + fields: Vec<(Field, Var, Type)>, + rhs: FringeEntry, +) -> T::SequenceItem { + let rhs_loc = rhs.var.loc; + let mut lvalue_fields: Fields<(Type, T::LValue)> = UniqueMap::new(); + + for (ndx, (field_name, var, ty)) in fields.into_iter().enumerate() { + let var_lvalue = make_lvalue(var, Mutability::Imm, ty.clone()); + let lhs_ty = sp(ty.loc, ty.value.base_type_()); + lvalue_fields + .add(field_name, (ndx, (lhs_ty, var_lvalue))) + .unwrap(); + } + + let unpack_lvalue_ = if let Some(mut_) = mut_ref { + T::LValue_::BorrowUnpack(mut_, mident, struct_, tyargs, lvalue_fields) + } else { + T::LValue_::Unpack(mident, struct_, tyargs, lvalue_fields) + }; + let rhs_ty = rhs.ty.clone(); + let rhs: Box = Box::new(rhs.into_move_exp()); + let binder = T::SequenceItem_::Bind( + sp(rhs_loc, vec![sp(rhs_loc, unpack_lvalue_)]), + vec![Some(rhs_ty)], + rhs, + ); + sp(rhs_loc, binder) +} + +fn make_match_lit(subject: FringeEntry) -> T::Exp { + let FringeEntry { var, ty } = subject; + match ty { + sp!(ty_loc, N::Type_::Ref(false, inner)) => { + let loc = var.loc; + let copy_exp = make_copy_exp(sp(ty_loc, N::Type_::Ref(false, inner.clone())), loc, var); + make_deref_exp(*inner, loc, copy_exp) + } + sp!(_, N::Type_::Ref(true, inner)) => { + let loc = var.loc; + + // NB(cswords): we now freeze the mut ref at the non-mut ref type. + let ref_ty = sp(loc, N::Type_::Ref(true, inner.clone())); + let freeze_arg = make_copy_exp(ref_ty, loc, var); + let freeze_ty = sp(loc, N::Type_::Ref(false, inner.clone())); + let frozen_exp = make_freeze_exp(freeze_ty, loc, freeze_arg); + make_deref_exp(*inner, loc, frozen_exp) + } + _ty => unreachable!(), + } +} + +fn make_copy_bindings(bindings: PatBindings, next: T::Exp) -> T::Exp { + make_bindings(bindings, next, true) +} + +fn make_bindings(bindings: PatBindings, next: T::Exp, as_copy: bool) -> T::Exp { + let eloc = next.exp.loc; + let mut seq = VecDeque::new(); + for (lhs, (mut_, rhs)) in bindings { + let binding = if as_copy { + make_copy_binding(lhs, mut_, rhs.ty.clone(), rhs) + } else { + make_move_binding(lhs, mut_, rhs.ty.clone(), rhs) + }; + seq.push_back(binding); + } + let result_type = next.ty.clone(); + seq.push_back(sp(eloc, T::SequenceItem_::Seq(Box::new(next)))); + let exp_value = sp(eloc, T::UnannotatedExp_::Block((UseFuns::new(0), seq))); + T::exp(result_type, exp_value) +} + +fn make_lvalue(lhs: Var, mut_: Mutability, ty: Type) -> T::LValue { + let lhs_loc = lhs.loc; + let lhs_var = T::LValue_::Var { + var: lhs, + ty: Box::new(ty.clone()), + mut_: Some(mut_), + unused_binding: false, + }; + sp(lhs_loc, lhs_var) +} + +fn make_move_binding(lhs: Var, mut_: Mutability, ty: Type, rhs: FringeEntry) -> T::SequenceItem { + let lhs_loc = lhs.loc; + let lhs_lvalue = make_lvalue(lhs, mut_, ty.clone()); + let binder = T::SequenceItem_::Bind( + sp(lhs_loc, vec![lhs_lvalue]), + vec![Some(ty)], + Box::new(rhs.into_move_exp()), + ); + sp(lhs_loc, binder) +} + +fn make_copy_binding(lhs: Var, mut_: Mutability, ty: Type, rhs: FringeEntry) -> T::SequenceItem { + let lhs_loc = lhs.loc; + let lhs_lvalue = make_lvalue(lhs, mut_, ty.clone()); + let binder = T::SequenceItem_::Bind( + sp(lhs_loc, vec![lhs_lvalue]), + vec![Some(ty.clone())], + Box::new(make_copy_exp(ty, rhs.var.loc, rhs.var)), + ); + sp(lhs_loc, binder) +} + +fn make_lit_test(lit_exp: T::Exp, value: Value) -> T::Exp { + let loc = value.loc; + let value_exp = T::exp( + lit_exp.ty.clone(), + sp(loc, T::UnannotatedExp_::Value(value)), + ); + make_eq_test(loc, lit_exp, value_exp) +} + +fn make_if_else(test: T::Exp, conseq: T::Exp, alt: T::Exp, result_ty: Type) -> T::Exp { + // FIXME: this span is woefully wrong + let loc = test.exp.loc; + T::exp( + result_ty, + sp( + loc, + T::UnannotatedExp_::IfElse(Box::new(test), Box::new(conseq), Box::new(alt)), + ), + ) +} + +fn make_copy_exp(ty: Type, loc: Loc, var: Var) -> T::Exp { + let exp_ = T::UnannotatedExp_::Copy { + var, + from_user: false, + }; + T::exp(ty, sp(loc, exp_)) +} + +fn make_freeze_exp(ty: Type, loc: Loc, arg: T::Exp) -> T::Exp { + let freeze_fn = Box::new(sp(loc, T::BuiltinFunction_::Freeze(ty.clone()))); + let freeze_exp = T::UnannotatedExp_::Builtin(freeze_fn, Box::new(arg)); + T::exp(ty, sp(loc, freeze_exp)) +} + +fn make_deref_exp(ty: Type, loc: Loc, arg: T::Exp) -> T::Exp { + let deref_exp = T::UnannotatedExp_::Dereference(Box::new(arg)); + T::exp(ty, sp(loc, deref_exp)) +} + +//************************************************************************************************** +// Debug Print +//************************************************************************************************** + +impl AstDebug for PatternMatrix { + fn ast_debug(&self, w: &mut AstWriter) { + for arm in &self.patterns { + let PatternArm { + pats: pat, + guard, + arm, + } = arm; + w.write(" { "); + w.comma(pat, |w, p| p.ast_debug(w)); + w.write(" } =>"); + if let Some(guard) = guard { + w.write(" if "); + guard.ast_debug(w); + } + w.write(" ["); + w.write(format!("] arm {}\n", arm.index)); + } + } +} + +impl AstDebug for FringeEntry { + fn ast_debug(&self, w: &mut AstWriter) { + w.write(format!("{:?} : ", self.var)); + self.ty.ast_debug(w); + } +} diff --git a/external-crates/move/crates/move-compiler/src/hlir/mod.rs b/external-crates/move/crates/move-compiler/src/hlir/mod.rs index f1ffbd406bd8a..63f786373ac17 100644 --- a/external-crates/move/crates/move-compiler/src/hlir/mod.rs +++ b/external-crates/move/crates/move-compiler/src/hlir/mod.rs @@ -4,4 +4,5 @@ pub mod ast; pub(crate) mod detect_dead_code; +pub(crate) mod match_compilation; pub(crate) mod translate; diff --git a/external-crates/move/crates/move-compiler/src/hlir/translate.rs b/external-crates/move/crates/move-compiler/src/hlir/translate.rs index 25e5a3a864927..b23e5909f1fad 100644 --- a/external-crates/move/crates/move-compiler/src/hlir/translate.rs +++ b/external-crates/move/crates/move-compiler/src/hlir/translate.rs @@ -9,13 +9,21 @@ use crate::{ hlir::{ ast::{self as H, Block, BlockLabel, MoveOpAnnotation, UnpackType}, detect_dead_code::program as detect_dead_code_analysis, + match_compilation, }, ice, naming::ast as N, parser::ast::{ Ability_, BinOp, BinOp_, ConstantName, DatatypeName, Field, FunctionName, VariantName, }, - shared::{process_binops, string_utils::debug_print, unique_map::UniqueMap, *}, + shared::{ + matching::{new_match_var_name, MatchContext, MATCH_TEMP_PREFIX}, + process_binops, + program_info::TypingProgramInfo, + string_utils::debug_print, + unique_map::UniqueMap, + *, + }, sui_mode::ID_FIELD_NAME, typing::ast as T, FullyCompiledProgram, @@ -68,9 +76,6 @@ fn translate_block_label(lbl: N::BlockLabel) -> H::BlockLabel { const TEMP_PREFIX: &str = "%"; static TEMP_PREFIX_SYMBOL: Lazy = Lazy::new(|| TEMP_PREFIX.into()); -const MATCH_TEMP_PREFIX: &str = "__match_tmp%"; -pub static MATCH_TEMP_PREFIX_SYMBOL: Lazy = Lazy::new(|| MATCH_TEMP_PREFIX.into()); - fn new_temp_name(context: &mut Context) -> Symbol { format!( "{}{}{}", @@ -113,23 +118,20 @@ pub fn display_var(s: Symbol) -> DisplayVar { // Context //************************************************************************************************** -type VariantFieldIndicies = UniqueMap< - ModuleIdent, - UniqueMap>>, ->; - pub(super) struct HLIRDebugFlags { pub(super) match_variant_translation: bool, + pub(super) match_translation: bool, + pub(super) match_specialization: bool, + pub(super) match_work_queue: bool, pub(super) function_translation: bool, pub(super) eval_order: bool, } pub(super) struct Context<'env> { pub env: &'env mut CompilationEnv, + pub info: Arc, pub debug: HLIRDebugFlags, current_package: Option, - structs: UniqueMap>>, - variant_fields: VariantFieldIndicies, function_locals: UniqueMap, signature: Option, tmp_counter: usize, @@ -142,122 +144,22 @@ pub(super) struct Context<'env> { impl<'env> Context<'env> { pub fn new( env: &'env mut CompilationEnv, - pre_compiled_lib_opt: Option>, + _pre_compiled_lib_opt: Option>, prog: &T::Program, ) -> Self { - fn add_struct_fields( - env: &mut CompilationEnv, - structs: &mut UniqueMap>>, - mident: ModuleIdent, - struct_defs: &UniqueMap, - ) { - let mut cur_structs = UniqueMap::new(); - let mut cur_structs_positional_info = UniqueMap::new(); - for (sname, sdef) in struct_defs.key_cloned_iter() { - let mut fields = UniqueMap::new(); - let field_map = match &sdef.fields { - N::StructFields::Native(_) => continue, - N::StructFields::Defined(fields_are_positional, m) => { - cur_structs_positional_info - .add(sname, *fields_are_positional) - .unwrap(); - m - } - }; - for (field, (idx, _)) in field_map.key_cloned_iter() { - fields.add(field, *idx).unwrap(); - } - cur_structs.add(sname, fields).unwrap(); - } - if let Err((_, prev_loc)) = structs.add(mident, cur_structs) { - let mut diag = ice!(( - mident.loc, - format!("Structs for module {} redefined here", mident) - )); - diag.add_secondary_label((prev_loc, "Previously defined here")); - env.add_diag(diag); - } - } - - fn add_enums( - env: &mut CompilationEnv, - variant_fields: &mut VariantFieldIndicies, - mident: ModuleIdent, - enum_defs: &UniqueMap, - ) { - let mut cur_enums_variants = UniqueMap::new(); - let mut cur_enums_variant_fields = UniqueMap::new(); - let mut cur_enums_variant_positional_info = UniqueMap::new(); - for (ename, edef) in enum_defs.key_cloned_iter() { - let mut enum_variant_fields = UniqueMap::new(); - let mut enum_variant_positional_info = UniqueMap::new(); - let mut enum_indexed_variants = vec![]; - for (variant_name, vdef) in edef.variants.key_cloned_iter() { - let fields = match &vdef.fields { - N::VariantFields::Empty => UniqueMap::new(), - N::VariantFields::Defined(fields_are_positional, m) => { - enum_variant_positional_info - .add(variant_name, *fields_are_positional) - .unwrap(); - UniqueMap::maybe_from_iter( - m.key_cloned_iter().map(|(field, (idx, _))| (field, *idx)), - ) - .unwrap() - } - }; - enum_indexed_variants.push((variant_name, vdef.index)); - enum_variant_fields.add(variant_name, fields).unwrap(); - } - enum_indexed_variants.sort_by(|(_, ndx0), (_, ndx1)| ndx0.cmp(ndx1)); - cur_enums_variants - .add( - ename, - enum_indexed_variants - .into_iter() - .map(|(key, _ndx)| key) - .collect::>(), - ) - .unwrap(); - cur_enums_variant_fields - .add(ename, enum_variant_fields) - .unwrap(); - cur_enums_variant_positional_info - .add(ename, enum_variant_positional_info) - .unwrap(); - } - if let Err((_, prev_loc)) = variant_fields.add(mident, cur_enums_variant_fields) { - let mut diag = ice!(( - mident.loc, - format!("Variants for module {} redefined here", mident) - )); - diag.add_secondary_label((prev_loc, "Previously defined here")); - env.add_diag(diag); - } - } - - let mut structs = UniqueMap::new(); - let mut variant_fields = UniqueMap::new(); - if let Some(pre_compiled_lib) = pre_compiled_lib_opt { - for (mident, mdef) in pre_compiled_lib.typing.modules.key_cloned_iter() { - add_struct_fields(env, &mut structs, mident, &mdef.structs); - add_enums(env, &mut variant_fields, mident, &mdef.enums); - } - } - for (mident, mdef) in prog.modules.key_cloned_iter() { - add_struct_fields(env, &mut structs, mident, &mdef.structs); - add_enums(env, &mut variant_fields, mident, &mdef.enums); - } let debug = HLIRDebugFlags { match_variant_translation: false, function_translation: false, eval_order: false, + match_translation: false, + match_specialization: false, + match_work_queue: false, }; Context { env, + info: prog.info.clone(), debug, current_package: None, - structs, - variant_fields, function_locals: UniqueMap::new(), signature: None, tmp_counter: 0, @@ -329,38 +231,6 @@ impl<'env> Context<'env> { self.named_block_types.get(block_name).cloned() } - pub fn struct_fields( - &self, - module: &ModuleIdent, - struct_name: &DatatypeName, - ) -> Option<&UniqueMap> { - let fields = self - .structs - .get(module) - .and_then(|structs| structs.get(struct_name)); - // if fields are none, the struct must be defined in another module, - // in that case, there should be errors - assert!(fields.is_some() || self.env.has_errors()); - fields - } - - pub fn enum_variant_fields( - &self, - module: &ModuleIdent, - enum_name: &DatatypeName, - variant_name: &VariantName, - ) -> Option<&UniqueMap> { - let fields = self - .variant_fields - .get(module) - .and_then(|enums| enums.get(enum_name)) - .and_then(|variants| variants.get(variant_name)); - // if fields are none, the variant must be defined in another module, - // in that case, there should be errors - assert!(fields.is_some() || self.env.has_errors()); - fields - } - fn counter_next(&mut self) -> usize { self.tmp_counter += 1; self.tmp_counter @@ -373,6 +243,40 @@ impl<'env> Context<'env> { } } +impl MatchContext for Context<'_> { + fn env(&mut self) -> &mut CompilationEnv { + self.env + } + + fn env_ref(&self) -> &CompilationEnv { + self.env + } + + /// Makes a new `naming/ast.rs` variable. Does _not_ record it as a function local, since this + /// should only be called in match compilation, which will have its body processed in HLIR + /// translation after expansion. + fn new_match_var(&mut self, name: String, loc: Loc) -> N::Var { + let id = self.counter_next(); + let name = new_match_var_name(&name, id); + // NOTE: this color is "wrong" insofar as it really should reflect whatever the current + // color scope is. Since these are only used as match temporaries, however, and they have + // names that may not be written as input, it's impossible for these to shadow macro + // argument names. + sp( + loc, + N::Var_ { + name, + id: id as u16, + color: 0, + }, + ) + } + + fn program_info(&self) -> &program_info::ProgramInfo { + self.info.as_ref() + } +} + //************************************************************************************************** // Entry //************************************************************************************************** @@ -921,9 +825,18 @@ fn tail( } } - E::Match(_subject, _arms) => { - context.env.add_diag(ice!((eloc, "ICE unexpanded match"))); - None + E::Match(subject, arms) => { + debug_print!(context.debug.match_translation, + ("subject" => subject), + (lines "arms" => &arms.value) + ); + let compiled = match_compilation::compile_match(context, in_type, *subject, arms); + debug_print!(context.debug.match_translation, ("compiled" => compiled)); + let result = tail(context, block, expected_type, compiled); + debug_print!(context.debug.match_variant_translation, + (lines "block" => block; verbose), + (opt "result" => &result)); + result } E::VariantMatch(subject, (_module, enum_name), arms) => { @@ -1256,6 +1169,18 @@ fn value( } } + E::Match(subject, arms) => { + debug_print!(context.debug.match_translation, + ("subject" => subject), + (lines "arms" => &arms.value) + ); + let compiled = match_compilation::compile_match(context, in_type, *subject, arms); + debug_print!(context.debug.match_translation, ("compiled" => compiled)); + let result = value(context, block, None, compiled); + debug_print!(context.debug.match_variant_translation, ("result" => &result)); + result + } + E::VariantMatch(subject, (_module, enum_name), arms) => { let subject_out_type = type_(context, subject.ty.clone()); let subject = Box::new(value(context, block, Some(&subject_out_type), *subject)); @@ -1404,10 +1329,10 @@ fn value( let base_types = base_types(context, arg_types); - let decl_fields = context.struct_fields(&module_ident, &struct_name); + let decl_fields = context.info.struct_fields(&module_ident, &struct_name); let mut texp_fields: Vec<(usize, Field, usize, N::Type, T::Exp)> = - if let Some(field_map) = decl_fields { + if let Some(ref field_map) = decl_fields { fields .into_iter() .map(|(f, (exp_idx, (bt, tf)))| { @@ -1481,10 +1406,13 @@ fn value( E::PackVariant(module_ident, enum_name, variant_name, arg_types, fields) => { let base_types = base_types(context, arg_types); - let decl_fields = context.enum_variant_fields(&module_ident, &enum_name, &variant_name); + let decl_fields = + context + .info + .enum_variant_fields(&module_ident, &enum_name, &variant_name); let mut texp_fields: Vec<(usize, Field, usize, N::Type, T::Exp)> = - if let Some(field_map) = decl_fields { + if let Some(ref field_map) = decl_fields { fields .into_iter() .map(|(f, (exp_idx, (bt, tf)))| { @@ -1677,10 +1605,6 @@ fn value( context.env.add_diag(ice!((eloc, "ICE unexpanded use"))); error_exp(eloc) } - E::Match(_subject, _arms) => { - context.env.add_diag(ice!((eloc, "ICE unexpanded match"))); - error_exp(eloc) - } E::UnresolvedError => { assert!(context.env.has_errors()); make_exp(HE::UnresolvedError) @@ -1890,6 +1814,17 @@ fn statement(context: &mut Context, block: &mut Block, e: T::Exp) { }, )); } + E::Match(subject, arms) => { + debug_print!(context.debug.match_translation, + ("subject" => subject), + (lines "arms" => &arms.value) + ); + let subject_type = subject.ty.clone(); + let compiled = match_compilation::compile_match(context, &subject_type, *subject, arms); + debug_print!(context.debug.match_translation, ("compiled" => compiled)); + statement(context, block, compiled); + debug_print!(context.debug.match_variant_translation, (lines "block" => block)); + } E::VariantMatch(subject, (_module, enum_name), arms) => { let subject = Box::new(value(context, block, None, *subject)); let arms = arms @@ -2035,9 +1970,6 @@ fn statement(context: &mut Context, block: &mut Block, e: T::Exp) { E::Use(_) => { context.env.add_diag(ice!((eloc, "ICE unexpanded use"))); } - E::Match(_subject, _arms) => { - context.env.add_diag(ice!((eloc, "ICE unexpanded match"))); - } } } @@ -2390,7 +2322,7 @@ fn assign_struct_fields( s: &DatatypeName, tfields: Fields<(N::Type, T::LValue)>, ) -> Vec<(usize, Field, H::BaseType, T::LValue)> { - let decl_fields = context.struct_fields(m, s).cloned(); + let decl_fields = context.info.struct_fields(m, s); let mut tfields_vec: Vec<_> = match decl_fields { Some(m) => tfields .into_iter() @@ -2420,7 +2352,7 @@ fn assign_variant_fields( v: &VariantName, tfields: Fields<(N::Type, T::LValue)>, ) -> Vec<(usize, Field, H::BaseType, T::LValue)> { - let decl_fields = context.enum_variant_fields(m, e, v).cloned(); + let decl_fields = context.info.enum_variant_fields(m, e, v); let mut tfields_vec: Vec<_> = match decl_fields { Some(m) => tfields .into_iter() diff --git a/external-crates/move/crates/move-compiler/src/shared/matching.rs b/external-crates/move/crates/move-compiler/src/shared/matching.rs new file mode 100644 index 0000000000000..b905618ec9f36 --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/shared/matching.rs @@ -0,0 +1,992 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + expansion::ast::{Fields, ModuleIdent, Mutability, Value}, + hlir::translate::NEW_NAME_DELIM, + naming::ast::{self as N, Type, Var}, + parser::ast::{BinOp_, ConstantName, Field, VariantName}, + shared::{program_info::ProgramInfo, unique_map::UniqueMap, CompilationEnv}, + { + ice, + typing::ast::{self as T, MatchArm_, MatchPattern, UnannotatedPat_ as TP}, + }, +}; +use move_ir_types::location::*; +use move_proc_macros::growing_stack; +use move_symbol_pool::Symbol; +use std::collections::{BTreeMap, BTreeSet, VecDeque}; + +//************************************************************************************************** +// Pattern Matrix Definitions for Matching +//************************************************************************************************** + +#[derive(Clone, Debug)] +pub struct FringeEntry { + pub var: Var, + pub ty: Type, +} + +pub type Binders = Vec<(Mutability, Var)>; +pub type PatBindings = BTreeMap; +pub type Guard = Option>; + +#[derive(Clone, Debug)] +pub struct Arm { + pub orig_pattern: MatchPattern, + pub rhs_binders: BTreeSet, + pub index: usize, +} + +#[derive(Clone, Debug)] +pub struct PatternArm { + pub pats: VecDeque, + pub guard: Guard, + pub arm: Arm, +} + +#[derive(Clone, Debug)] +pub struct PatternMatrix { + pub tys: Vec, + pub patterns: Vec, +} + +#[derive(Clone, Debug)] +pub struct ArmResult { + pub loc: Loc, + pub bindings: PatBindings, + pub guard: Option>, + pub arm: Arm, +} + +//************************************************************************************************** +// Match Context +//************************************************************************************************** + +/// A shared match context trait for use with counterexample generation in Typing and match +/// compilation in HLIR lowering. +pub trait MatchContext { + fn env(&mut self) -> &mut CompilationEnv; + fn env_ref(&self) -> &CompilationEnv; + fn new_match_var(&mut self, name: String, loc: Loc) -> N::Var; + fn program_info(&self) -> &ProgramInfo; + + //******************************************** + // Helpers for Compiling Pattern Matricies + //******************************************** + + fn make_imm_ref_match_binders( + &mut self, + pattern_loc: Loc, + arg_types: Fields, + ) -> Vec<(Field, N::Var, N::Type)> { + fn make_imm_ref_ty(ty: N::Type) -> N::Type { + match ty { + sp!(_, N::Type_::Ref(false, _)) => ty, + sp!(loc, N::Type_::Ref(true, inner)) => sp(loc, N::Type_::Ref(false, inner)), + ty => { + let loc = ty.loc; + sp(loc, N::Type_::Ref(false, Box::new(ty))) + } + } + } + + let fields = order_fields_by_decl(None, arg_types.clone()); + fields + .into_iter() + .map(|(_, field_name, field_type)| { + ( + field_name, + self.new_match_var(field_name.to_string(), pattern_loc), + make_imm_ref_ty(field_type), + ) + }) + .collect::>() + } + + fn make_unpack_binders( + &mut self, + pattern_loc: Loc, + arg_types: Fields, + ) -> Vec<(Field, N::Var, N::Type)> { + let fields = order_fields_by_decl(None, arg_types.clone()); + fields + .into_iter() + .map(|(_, field_name, field_type)| { + ( + field_name, + self.new_match_var(field_name.to_string(), pattern_loc), + field_type, + ) + }) + .collect::>() + } +} + +//************************************************************************************************** +// Impls +//************************************************************************************************** + +impl FringeEntry { + pub fn into_move_exp(self) -> T::Exp { + let FringeEntry { var, ty } = self; + let move_exp = T::UnannotatedExp_::Move { + from_user: false, + var, + }; + T::exp(ty, sp(var.loc, move_exp)) + } +} + +impl PatternArm { + fn pattern_empty(&self) -> bool { + self.pats.is_empty() + } + + /// Returns true if every entry is a wildcard or binder. + fn is_wild_arm(&self) -> bool { + self.pats + .iter() + .all(|pat| matches!(pat.pat.value, TP::Wildcard | TP::Binder(_, _))) + } + + fn all_wild_arm(&mut self, fringe: &VecDeque) -> Option { + if self.is_wild_arm() { + let bindings = self.make_arm_bindings(fringe); + let PatternArm { + pats: _, + guard, + arm, + } = self; + let arm = ArmResult { + loc: arm.orig_pattern.pat.loc, + bindings, + guard: guard.clone(), + arm: arm.clone(), + }; + Some(arm) + } else { + None + } + } + + fn make_arm_bindings(&mut self, fringe: &VecDeque) -> PatBindings { + let mut bindings = BTreeMap::new(); + assert!(self.pats.len() == fringe.len()); + for (pmut, subject) in self.pats.iter_mut().zip(fringe.iter()) { + if let TP::Binder(mut_, x) = pmut.pat.value { + if bindings.insert(x, (mut_, subject.clone())).is_some() { + panic!("ICE should have failed in naming"); + }; + pmut.pat.value = TP::Wildcard; + } + } + bindings + } + + fn first_variant(&self) -> Option<(VariantName, (Loc, Fields))> { + if self.pats.is_empty() { + return None; + } + + fn first_variant_recur(pat: MatchPattern) -> Option<(VariantName, (Loc, Fields))> { + match pat.pat.value { + TP::Variant(_, _, name, _, fields) => { + let ty_fields: Fields = fields.clone().map(|_, (ndx, (ty, _))| (ndx, ty)); + Some((name, (pat.pat.loc, ty_fields))) + } + TP::BorrowVariant(_, _, _, name, _, fields) => { + let ty_fields: Fields = fields.clone().map(|_, (ndx, (ty, _))| (ndx, ty)); + Some((name, (pat.pat.loc, ty_fields))) + } + TP::At(_, inner) => first_variant_recur(*inner), + TP::Struct(..) | TP::BorrowStruct(..) => None, + TP::Constant(_, _) + | TP::Binder(_, _) + | TP::Literal(_) + | TP::Wildcard + | TP::ErrorPat => None, + TP::Or(_, _) => unreachable!(), + } + } + + first_variant_recur(self.pats.front().unwrap().clone()) + } + + fn first_struct(&self) -> Option<(Loc, Fields)> { + if self.pats.is_empty() { + return None; + } + + fn first_struct_recur(pat: MatchPattern) -> Option<(Loc, Fields)> { + match pat.pat.value { + TP::Struct(_, _, _, fields) => { + let ty_fields: Fields = fields.clone().map(|_, (ndx, (ty, _))| (ndx, ty)); + Some((pat.pat.loc, ty_fields)) + } + TP::BorrowStruct(_, _, _, _, fields) => { + let ty_fields: Fields = fields.clone().map(|_, (ndx, (ty, _))| (ndx, ty)); + Some((pat.pat.loc, ty_fields)) + } + TP::At(_, inner) => first_struct_recur(*inner), + TP::Variant(..) | TP::BorrowVariant(..) => None, + TP::Constant(_, _) + | TP::Binder(_, _) + | TP::Literal(_) + | TP::Wildcard + | TP::ErrorPat => None, + TP::Or(_, _) => unreachable!(), + } + } + + first_struct_recur(self.pats.front().unwrap().clone()) + } + + fn first_lit(&self) -> Option { + if self.pats.is_empty() { + return None; + } + + fn first_lit_recur(pat: MatchPattern) -> Option { + match pat.pat.value { + TP::Literal(v) => Some(v), + TP::At(_, inner) => first_lit_recur(*inner), + TP::Variant(_, _, _, _, _) + | TP::BorrowVariant(_, _, _, _, _, _) + | TP::Struct(..) + | TP::BorrowStruct(..) + | TP::Binder(_, _) + | TP::Wildcard + | TP::ErrorPat => None, + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + } + } + + first_lit_recur(self.pats.front().unwrap().clone()) + } + + fn specialize_variant>( + &self, + context: &MC, + ctor_name: &VariantName, + arg_types: &Vec<&Type>, + ) -> Option<(Binders, PatternArm)> { + let mut output = self.clone(); + let first_pattern = output.pats.pop_front().unwrap(); + let loc = first_pattern.pat.loc; + match first_pattern.pat.value { + TP::Variant(mident, enum_, name, _, fields) + | TP::BorrowVariant(_, mident, enum_, name, _, fields) + if &name == ctor_name => + { + let field_pats = fields.clone().map(|_key, (ndx, (_, pat))| (ndx, pat)); + let decl_fields = context + .program_info() + .enum_variant_fields(&mident, &enum_, &name); + let ordered_pats = order_fields_by_decl(decl_fields, field_pats); + for (_, _, pat) in ordered_pats.into_iter().rev() { + output.pats.push_front(pat); + } + Some((vec![], output)) + } + TP::Variant(_, _, _, _, _) | TP::BorrowVariant(_, _, _, _, _, _) => None, + TP::Struct(_, _, _, _) | TP::BorrowStruct(_, _, _, _, _) => None, + TP::Literal(_) => None, + TP::Binder(mut_, x) => { + for arg_type in arg_types + .clone() + .into_iter() + .map(|ty| ty_to_wildcard_pattern(ty.clone(), loc)) + .rev() + { + output.pats.push_front(arg_type); + } + Some((vec![(mut_, x)], output)) + } + TP::Wildcard => { + for arg_type in arg_types + .clone() + .into_iter() + .map(|ty| ty_to_wildcard_pattern(ty.clone(), loc)) + .rev() + { + output.pats.push_front(arg_type); + } + Some((vec![], output)) + } + TP::At(x, inner) => { + output.pats.push_front(*inner); + output + .specialize_variant(context, ctor_name, arg_types) + .map(|(mut binders, inner)| { + binders.push((Mutability::Imm, x)); + (binders, inner) + }) + } + TP::ErrorPat => None, + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + } + } + + fn specialize_struct>( + &self, + context: &MC, + arg_types: &Vec<&Type>, + ) -> Option<(Binders, PatternArm)> { + let mut output = self.clone(); + let first_pattern = output.pats.pop_front().unwrap(); + let loc = first_pattern.pat.loc; + match first_pattern.pat.value { + TP::Struct(mident, struct_, _, fields) + | TP::BorrowStruct(_, mident, struct_, _, fields) => { + let field_pats = fields.clone().map(|_key, (ndx, (_, pat))| (ndx, pat)); + let decl_fields = context.program_info().struct_fields(&mident, &struct_); + let ordered_pats = order_fields_by_decl(decl_fields, field_pats); + for (_, _, pat) in ordered_pats.into_iter().rev() { + output.pats.push_front(pat); + } + Some((vec![], output)) + } + TP::Literal(_) => None, + TP::Variant(_, _, _, _, _) | TP::BorrowVariant(_, _, _, _, _, _) => None, + TP::Binder(mut_, x) => { + for arg_type in arg_types + .clone() + .into_iter() + .map(|ty| ty_to_wildcard_pattern(ty.clone(), loc)) + .rev() + { + output.pats.push_front(arg_type); + } + Some((vec![(mut_, x)], output)) + } + TP::Wildcard => { + for arg_type in arg_types + .clone() + .into_iter() + .map(|ty| ty_to_wildcard_pattern(ty.clone(), loc)) + .rev() + { + output.pats.push_front(arg_type); + } + Some((vec![], output)) + } + TP::At(x, inner) => { + output.pats.push_front(*inner); + output + .specialize_struct(context, arg_types) + .map(|(mut binders, inner)| { + binders.push((Mutability::Imm, x)); + (binders, inner) + }) + } + TP::ErrorPat => None, + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + } + } + + fn specialize_literal(&self, literal: &Value) -> Option<(Binders, PatternArm)> { + let mut output = self.clone(); + let first_pattern = output.pats.pop_front().unwrap(); + match first_pattern.pat.value { + TP::Literal(v) if &v == literal => Some((vec![], output)), + TP::Literal(_) => None, + TP::Variant(_, _, _, _, _) | TP::BorrowVariant(_, _, _, _, _, _) => None, + TP::Struct(_, _, _, _) | TP::BorrowStruct(_, _, _, _, _) => None, + TP::Binder(mut_, x) => Some((vec![(mut_, x)], output)), + TP::Wildcard => Some((vec![], output)), + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + TP::At(x, inner) => { + output.pats.push_front(*inner); + output + .specialize_literal(literal) + .map(|(mut binders, inner)| { + binders.push((Mutability::Imm, x)); + (binders, inner) + }) + } + TP::ErrorPat => None, + } + } + + fn specialize_default(&self) -> Option<(Binders, PatternArm)> { + let mut output = self.clone(); + let first_pattern = output.pats.pop_front().unwrap(); + match first_pattern.pat.value { + TP::Literal(_) => None, + TP::Variant(_, _, _, _, _) | TP::BorrowVariant(_, _, _, _, _, _) => None, + TP::Struct(_, _, _, _) | TP::BorrowStruct(_, _, _, _, _) => None, + TP::Binder(mut_, x) => Some((vec![(mut_, x)], output)), + TP::Wildcard => Some((vec![], output)), + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + TP::At(x, inner) => { + output.pats.push_front(*inner); + output.specialize_default().map(|(mut binders, inner)| { + binders.push((Mutability::Imm, x)); + (binders, inner) + }) + } + TP::ErrorPat => None, + } + } +} + +impl PatternMatrix { + /// Converts a subject and match arms into a Pattern Matrix and the RHS arms (indexed by + /// position in the vector). This process works as follows: + /// - For each arm (pattern, guard, rhs): + /// - Add the RHS to the vector + /// - Split any OR patterns into their own pattern matrix entry, realizing each + /// independently. For each of these: + /// - Convert any CONSTANT patterns into a binding with a guard check for equality + /// - Store the original pattern on the entry, along with its RHS binders and arm index. + /// - Rewrite the pattern with the guard binders. + /// - Insert these resultant pattern into the pattern matrix, using the RHS index as the + /// index for all of them. + pub fn from>( + context: &mut MC, + subject_ty: Type, + arms: Vec, + ) -> (PatternMatrix, Vec) { + let tys = vec![subject_ty]; + let mut patterns = vec![]; + let mut rhss = vec![]; + for sp!(_, arm) in arms { + let MatchArm_ { + pattern, + binders: _, + guard, + guard_binders, + rhs_binders, + rhs, + } = arm; + rhss.push(*rhs); + let index = rhss.len() - 1; + let new_patterns = flatten_or(pattern); + for mut pat in new_patterns { + let (guard, const_binders) = const_pats_to_guards(context, &mut pat, guard.clone()); + let arm = Arm { + orig_pattern: pat.clone(), + rhs_binders: rhs_binders.clone(), + index, + }; + // Make a match pattern that only holds guard binders + let guard_binders = guard_binders.union_with(&const_binders, |k, _, x| { + let msg = "Match compilation made a binder for this during const compilation"; + context.env().add_diag(ice!((k.loc, msg))); + *x + }); + let pat = apply_pattern_subst(pat, &guard_binders); + patterns.push(PatternArm { + pats: VecDeque::from([pat]), + guard, + arm, + }); + } + } + (PatternMatrix { tys, patterns }, rhss) + } + + pub fn is_empty(&self) -> bool { + self.patterns.is_empty() + } + + pub fn patterns_empty(&self) -> bool { + !self.patterns.is_empty() && self.patterns.iter().all(|pat| pat.pattern_empty()) + } + + pub fn all_errors(&self) -> bool { + self.patterns.iter().all(|arm| { + arm.pats + .iter() + .all(|pat| matches!(pat.pat.value, TP::ErrorPat)) + }) + } + + /// Returns true if there is an arm made up entirely of wildcards / binders with no guard. + pub fn has_default_arm(&self) -> bool { + self.patterns + .iter() + .any(|pat| pat.is_wild_arm() && pat.guard.is_none()) + } + + pub fn wild_arm_opt(&mut self, fringe: &VecDeque) -> Option> { + // NB: If the first row is all wild, we need to collect _all_ wild rows that have guards + // until we find one that does not. + if let Some(arm) = self.patterns[0].all_wild_arm(fringe) { + if arm.guard.is_none() { + return Some(vec![arm]); + } + let mut result = vec![arm]; + for pat in self.patterns[1..].iter_mut() { + if let Some(arm) = pat.all_wild_arm(fringe) { + let has_guard = arm.guard.is_some(); + result.push(arm); + if !has_guard { + return Some(result); + } + } + } + Some(result) + } else { + None + } + } + + pub fn specialize_variant>( + &self, + context: &MC, + ctor_name: &VariantName, + arg_types: Vec<&Type>, + ) -> (Binders, PatternMatrix) { + let mut patterns = vec![]; + let mut bindings = vec![]; + for entry in &self.patterns { + if let Some((mut new_bindings, arm)) = + entry.specialize_variant(context, ctor_name, &arg_types) + { + bindings.append(&mut new_bindings); + patterns.push(arm) + } + } + let tys = arg_types + .into_iter() + .cloned() + .chain(self.tys.clone().into_iter().skip(1)) + .collect::>(); + let matrix = PatternMatrix { tys, patterns }; + (bindings, matrix) + } + + pub fn specialize_struct>( + &self, + context: &MC, + arg_types: Vec<&Type>, + ) -> (Binders, PatternMatrix) { + let mut patterns = vec![]; + let mut bindings = vec![]; + for entry in &self.patterns { + if let Some((mut new_bindings, arm)) = entry.specialize_struct(context, &arg_types) { + bindings.append(&mut new_bindings); + patterns.push(arm) + } + } + let tys = arg_types + .into_iter() + .cloned() + .chain(self.tys.clone().into_iter().skip(1)) + .collect::>(); + let matrix = PatternMatrix { tys, patterns }; + (bindings, matrix) + } + + pub fn specialize_literal(&self, lit: &Value) -> (Binders, PatternMatrix) { + let mut patterns = vec![]; + let mut bindings = vec![]; + for entry in &self.patterns { + if let Some((mut new_bindings, arm)) = entry.specialize_literal(lit) { + bindings.append(&mut new_bindings); + patterns.push(arm) + } + } + let tys = self.tys[1..].to_vec(); + let matrix = PatternMatrix { tys, patterns }; + (bindings, matrix) + } + + pub fn specialize_default(&self) -> (Binders, PatternMatrix) { + let mut patterns = vec![]; + let mut bindings = vec![]; + for entry in &self.patterns { + if let Some((mut new_bindings, arm)) = entry.specialize_default() { + bindings.append(&mut new_bindings); + patterns.push(arm) + } + } + let tys = self.tys[1..].to_vec(); + let matrix = PatternMatrix { tys, patterns }; + (bindings, matrix) + } + + pub fn first_variant_ctors(&self) -> BTreeMap)> { + self.patterns + .iter() + .flat_map(|pat| pat.first_variant()) + .collect() + } + + pub fn first_struct_ctors(&self) -> Option<(Loc, Fields)> { + self.patterns.iter().find_map(|pat| pat.first_struct()) + } + + pub fn first_lits(&self) -> BTreeSet { + self.patterns + .iter() + .flat_map(|pat| pat.first_lit()) + .collect() + } + + pub fn has_guards(&self) -> bool { + self.patterns.iter().any(|pat| pat.guard.is_some()) + } + + pub fn remove_guarded_arms(&mut self) { + let pats = std::mem::take(&mut self.patterns); + self.patterns = pats.into_iter().filter(|pat| pat.guard.is_none()).collect(); + } +} + +//************************************************************************************************** +// Helper Functions +//************************************************************************************************** + +fn ty_to_wildcard_pattern(ty: Type, loc: Loc) -> T::MatchPattern { + T::MatchPattern { + ty, + pat: sp(loc, T::UnannotatedPat_::Wildcard), + } +} + +// NB: this converts any binders not in `env` to wildcards, and strips any `at` pattern binders +// that is not in the `env` +fn apply_pattern_subst(pat: MatchPattern, env: &UniqueMap) -> MatchPattern { + let MatchPattern { + ty, + pat: sp!(ploc, pat), + } = pat; + let new_pat = match pat { + TP::Variant(m, e, v, ta, spats) => { + let out_fields = + spats.map(|_, (ndx, (t, pat))| (ndx, (t, apply_pattern_subst(pat, env)))); + TP::Variant(m, e, v, ta, out_fields) + } + TP::BorrowVariant(mut_, m, e, v, ta, spats) => { + let out_fields = + spats.map(|_, (ndx, (t, pat))| (ndx, (t, apply_pattern_subst(pat, env)))); + TP::BorrowVariant(mut_, m, e, v, ta, out_fields) + } + TP::Struct(m, s, ta, spats) => { + let out_fields = + spats.map(|_, (ndx, (t, pat))| (ndx, (t, apply_pattern_subst(pat, env)))); + TP::Struct(m, s, ta, out_fields) + } + TP::BorrowStruct(mut_, m, s, ta, spats) => { + let out_fields = + spats.map(|_, (ndx, (t, pat))| (ndx, (t, apply_pattern_subst(pat, env)))); + TP::BorrowStruct(mut_, m, s, ta, out_fields) + } + TP::At(x, inner) => { + let xloc = x.loc; + // Since we are only applying the guard environment, this may be unused here. + // If it is, we simply elide the `@` form. + if let Some(y) = env.get(&x) { + TP::At( + sp(xloc, y.value), + Box::new(apply_pattern_subst(*inner, env)), + ) + } else { + apply_pattern_subst(*inner, env).pat.value + } + } + TP::Binder(mut_, x) => { + let xloc = x.loc; + if let Some(y) = env.get(&x) { + TP::Binder(mut_, sp(xloc, y.value)) + } else { + TP::Wildcard + } + } + pat @ (TP::Literal(_) | TP::ErrorPat | TP::Wildcard) => pat, + TP::Constant(_, _) | TP::Or(_, _) => unreachable!(), + }; + MatchPattern { + ty, + pat: sp(ploc, new_pat), + } +} + +fn flatten_or(pat: MatchPattern) -> Vec { + let ploc = pat.pat.loc; + match pat.pat.value { + TP::Constant(_, _) | TP::Literal(_) | TP::Binder(_, _) | TP::Wildcard | TP::ErrorPat => { + vec![pat] + } + TP::Variant(_, _, _, _, ref pats) + | TP::BorrowVariant(_, _, _, _, _, ref pats) + | TP::Struct(_, _, _, ref pats) + | TP::BorrowStruct(_, _, _, _, ref pats) + if pats.is_empty() => + { + vec![pat] + } + TP::Variant(m, e, v, ta, spats) => { + let all_spats = spats.map(|_, (ndx, (t, pat))| (ndx, (t, flatten_or(pat)))); + let fields_lists: Vec> = combine_pattern_fields(all_spats); + fields_lists + .into_iter() + .map(|field_list| MatchPattern { + ty: pat.ty.clone(), + pat: sp(ploc, TP::Variant(m, e, v, ta.clone(), field_list)), + }) + .collect::>() + } + TP::BorrowVariant(mut_, m, e, v, ta, spats) => { + let all_spats = spats.map(|_, (ndx, (t, pat))| (ndx, (t, flatten_or(pat)))); + let fields_lists: Vec> = combine_pattern_fields(all_spats); + fields_lists + .into_iter() + .map(|field_list| MatchPattern { + ty: pat.ty.clone(), + pat: sp( + ploc, + TP::BorrowVariant(mut_, m, e, v, ta.clone(), field_list), + ), + }) + .collect::>() + } + TP::Struct(m, s, ta, spats) => { + let all_spats = spats.map(|_, (ndx, (t, pat))| (ndx, (t, flatten_or(pat)))); + let fields_lists: Vec> = combine_pattern_fields(all_spats); + fields_lists + .into_iter() + .map(|field_list| MatchPattern { + ty: pat.ty.clone(), + pat: sp(ploc, TP::Struct(m, s, ta.clone(), field_list)), + }) + .collect::>() + } + TP::BorrowStruct(mut_, m, s, ta, spats) => { + let all_spats = spats.map(|_, (ndx, (t, pat))| (ndx, (t, flatten_or(pat)))); + let fields_lists: Vec> = combine_pattern_fields(all_spats); + fields_lists + .into_iter() + .map(|field_list| MatchPattern { + ty: pat.ty.clone(), + pat: sp(ploc, TP::BorrowStruct(mut_, m, s, ta.clone(), field_list)), + }) + .collect::>() + } + TP::Or(lhs, rhs) => { + let mut lhs_rec = flatten_or(*lhs); + let mut rhs_rec = flatten_or(*rhs); + lhs_rec.append(&mut rhs_rec); + lhs_rec + } + TP::At(x, inner) => flatten_or(*inner) + .into_iter() + .map(|pat| MatchPattern { + ty: pat.ty.clone(), + pat: sp(ploc, TP::At(x, Box::new(pat))), + }) + .collect::>(), + } +} + +// A BRIEF OVERVIEW OF CONSTANT MATCH HANDLING +// +// To handle constants, we need to do two things: first, we need to replace all of the constants in +// the patterns with _something_, and then we need to push the constant checks into guards in the +// right-hand side. We take advantage of existing assumptions for match compilation to accomplish +// this, allowing us to reuse the existing match compilation machinery: +// +// 1. We traverse the pattern mutably, replacing all constants with new, fresh variables. +// 2. We generate a new 'guard' variable that acts as the "guard variable map" entry for that +// binder, indicating that this guard variable should be bound during match decision tree +// compilation for guard checking. This guard variable, as all, is typed as an immutable +// reference of the value in question. +// 3. We generate a guard check `guard_var == &const`. +// +// Finally, we hand back: +// +// 1. a new guard expression made up of the new guards plus the old guard (if any), in that order; +// 2. and a guard binder map that maps the new pattern variable to their new guard versions. +// +// As an example: +// +// match (Option::Some(5)) { +// Option::Some(y @ CONST) if (y#guard == 0) => rhs0, +// Option::Some(x) if (x#guard == 1) => rhs1, +// _ => rhs2 +// } +// +// will be translated as: +// +// match (Option::Some(5)) { +// Option::Some(y @ _match_var) if (_match_var#guard == &CONST && y#guard == 0) => rhs0, +// Option::Some(x) if (x#guard == 1) => rhs1, +// _ => rhs2 +// } +// +// At this point, match compilation can proceed normally. +// +// NB: Since `_match_var` is not in the `rhs_binders` list, it will be erased in the final arm. + +/// Assumes `flatten_or` has already been performed. +fn const_pats_to_guards>( + context: &mut MC, + pat: &mut MatchPattern, + guard: Option>, +) -> (Option>, UniqueMap) { + #[growing_stack] + fn convert_recur>( + context: &mut MC, + input: &mut MatchPattern, + guard_exps: &mut Vec, + guard_map: &mut UniqueMap, + ) { + match &mut input.pat.value { + TP::Constant(m, const_) => { + let loc = input.pat.loc; + let pat_var = context.new_match_var("const".to_string(), loc); + let guard_var = context.new_match_var("const_guard".to_string(), loc); + guard_map.add(pat_var, guard_var).expect("This cannot fail"); + let guard_exp = make_const_test(input.ty.clone(), guard_var, loc, *m, *const_); + let MatchPattern { ty, pat: _ } = input; + guard_exps.push(guard_exp); + *input = T::pat(ty.clone(), sp(loc, TP::Binder(Mutability::Imm, pat_var))); + } + TP::Variant(_, _, _, _, fields) + | TP::BorrowVariant(_, _, _, _, _, fields) + | TP::Struct(_, _, _, fields) + | TP::BorrowStruct(_, _, _, _, fields) => { + for (_, _, (_, (_, rhs))) in fields.iter_mut() { + convert_recur(context, rhs, guard_exps, guard_map); + } + } + TP::At(_, inner) => convert_recur(context, inner, guard_exps, guard_map), + TP::Literal(_) | TP::Binder(_, _) | TP::Wildcard | TP::ErrorPat => (), + TP::Or(_, _) => unreachable!(), + } + } + + fn combine_guards(mut guards: Vec, guard: Option>) -> Option> { + if let Some(guard) = guard { + guards.push(*guard); + } + let Some(mut guard) = guards.pop() else { + return None; + }; + while let Some(new_guard) = guards.pop() { + // FIXME: No good `Loc` to use here... + guard = make_and_test(new_guard.exp.loc, new_guard, guard); + } + Some(Box::new(guard)) + } + + let mut const_guards = vec![]; + let mut const_guard_map = UniqueMap::new(); + + convert_recur(context, pat, &mut const_guards, &mut const_guard_map); + (combine_guards(const_guards, guard), const_guard_map) +} + +fn combine_pattern_fields( + fields: Fields<(Type, Vec)>, +) -> Vec> { + type VFields = Vec<(Field, (usize, (Spanned, MatchPattern)))>; + type VVFields = Vec<(Field, (usize, (Spanned, Vec)))>; + + fn combine_recur(vec: &mut VVFields) -> Vec { + if let Some((f, (ndx, (ty, pats)))) = vec.pop() { + let rec_fields = combine_recur(vec); + let mut output = vec![]; + for entry in rec_fields { + for pat in pats.clone() { + let mut entry = entry.clone(); + entry.push((f, (ndx, (ty.clone(), pat)))); + output.push(entry); + } + } + output + } else { + // Base case: a single match of no fields. We must have at least one, or else we would + // not have called `combine_match_patterns`. + vec![vec![]] + } + } + + let mut vvfields: VVFields = fields.into_iter().collect::>(); + let output_vec = combine_recur(&mut vvfields); + output_vec + .into_iter() + .map(|vfields| UniqueMap::maybe_from_iter(vfields.into_iter()).unwrap()) + .collect::>() +} + +/// Helper function for creating an ordered list of fields Field information and Fields. +pub fn order_fields_by_decl( + decl_fields: Option>, + fields: Fields, +) -> Vec<(usize, Field, T)> { + let mut texp_fields: Vec<(usize, Field, T)> = if let Some(field_map) = decl_fields { + fields + .into_iter() + .map(|(f, (_exp_idx, t))| (*field_map.get(&f).unwrap(), f, t)) + .collect() + } else { + // If no field map, compiler error in typing. + fields + .into_iter() + .enumerate() + .map(|(ndx, (f, (_exp_idx, t)))| (ndx, f, t)) + .collect() + }; + texp_fields.sort_by(|(decl_idx1, _, _), (decl_idx2, _, _)| decl_idx1.cmp(decl_idx2)); + texp_fields +} + +pub const MATCH_TEMP_PREFIX: &str = "__match_tmp%"; + +// Expression Creation Helpers +// NOTE: this _must_ be a string that a user cannot write, as otherwise we could incorrectly shadow +// macro-expanded names. +/// Create a new name for match variable usage. +pub fn new_match_var_name(name: &str, id: usize) -> Symbol { + format!("{MATCH_TEMP_PREFIX}{NEW_NAME_DELIM}{name}{NEW_NAME_DELIM}{id}").into() +} + +// Since these are guards, we need to borrow the constant +fn make_const_test(ty: N::Type, var: N::Var, loc: Loc, m: ModuleIdent, c: ConstantName) -> T::Exp { + use T::UnannotatedExp_ as E; + let base_ty = sp(loc, ty.value.base_type_()); + let ref_ty = sp(loc, N::Type_::Ref(false, Box::new(base_ty.clone()))); + let var_exp = T::exp( + ref_ty.clone(), + sp( + loc, + E::Move { + from_user: false, + var, + }, + ), + ); + let const_exp = { + // We're in a guard, so we need to borrow the constant immutable. + let const_exp = T::exp(base_ty, sp(loc, E::Constant(m, c))); + let borrow_exp = E::TempBorrow(false, Box::new(const_exp)); + Box::new(T::exp(ref_ty, sp(loc, borrow_exp))) + }; + make_eq_test(loc, var_exp, *const_exp) +} + +pub fn make_eq_test(loc: Loc, lhs: T::Exp, rhs: T::Exp) -> T::Exp { + let bool = N::Type_::bool(loc); + let equality_exp_ = T::UnannotatedExp_::BinopExp( + Box::new(lhs), + sp(loc, BinOp_::Eq), + Box::new(bool.clone()), + Box::new(rhs), + ); + T::exp(bool, sp(loc, equality_exp_)) +} + +fn make_and_test(loc: Loc, lhs: T::Exp, rhs: T::Exp) -> T::Exp { + let bool = N::Type_::bool(loc); + let equality_exp_ = T::UnannotatedExp_::BinopExp( + Box::new(lhs), + sp(loc, BinOp_::And), + Box::new(bool.clone()), + Box::new(rhs), + ); + T::exp(bool, sp(loc, equality_exp_)) +} diff --git a/external-crates/move/crates/move-compiler/src/shared/mod.rs b/external-crates/move/crates/move-compiler/src/shared/mod.rs index 565b40f0f05b5..b846b0daa1358 100644 --- a/external-crates/move/crates/move-compiler/src/shared/mod.rs +++ b/external-crates/move/crates/move-compiler/src/shared/mod.rs @@ -48,6 +48,7 @@ use vfs::{VfsError, VfsPath}; pub mod ast_debug; pub mod ide; pub mod known_attributes; +pub mod matching; pub mod program_info; pub mod remembering_unique_map; pub mod string_utils; diff --git a/external-crates/move/crates/move-compiler/src/shared/program_info.rs b/external-crates/move/crates/move-compiler/src/shared/program_info.rs index 45ba1c9715509..0f4efcc271004 100644 --- a/external-crates/move/crates/move-compiler/src/shared/program_info.rs +++ b/external-crates/move/crates/move-compiler/src/shared/program_info.rs @@ -12,7 +12,7 @@ use crate::{ self as N, DatatypeTypeParameter, EnumDefinition, FunctionSignature, ResolvedUseFuns, StructDefinition, SyntaxMethods, Type, }, - parser::ast::{ConstantName, DatatypeName, FunctionName}, + parser::ast::{ConstantName, DatatypeName, Field, FunctionName, VariantName}, shared::unique_map::UniqueMap, shared::*, typing::ast::{self as T}, @@ -133,6 +133,30 @@ impl NamingProgramInfo { let mut module_use_funs: Option<&mut BTreeMap> = None; program_info!(pre_compiled_lib, prog, naming, module_use_funs) } + + pub fn set_use_funs(&mut self, module_use_funs: BTreeMap) { + for (mident, use_funs) in module_use_funs { + let use_funs_ref = &mut self.modules.get_mut(&mident).unwrap().use_funs; + assert!(use_funs_ref.is_empty()); + *use_funs_ref = use_funs; + } + } + + pub fn take_use_funs(self) -> BTreeMap { + self.modules + .into_iter() + .map(|(mident, minfo)| (mident, minfo.use_funs)) + .collect() + } + + pub fn set_module_syntax_methods( + &mut self, + mident: ModuleIdent, + syntax_methods: SyntaxMethods, + ) { + let syntax_methods_ref = &mut self.modules.get_mut(&mident).unwrap().syntax_methods; + *syntax_methods_ref = syntax_methods; + } } impl ProgramInfo { @@ -228,30 +252,103 @@ impl ProgramInfo { let constants = &self.module(m).constants; constants.get(n).expect("ICE should have failed in naming") } -} -impl NamingProgramInfo { - pub fn set_use_funs(&mut self, module_use_funs: BTreeMap) { - for (mident, use_funs) in module_use_funs { - let use_funs_ref = &mut self.modules.get_mut(&mident).unwrap().use_funs; - assert!(use_funs_ref.is_empty()); - *use_funs_ref = use_funs; + pub fn is_struct(&self, module: &ModuleIdent, datatype_name: &DatatypeName) -> bool { + matches!( + self.datatype_kind(module, datatype_name), + DatatypeKind::Struct + ) + } + + pub fn struct_fields( + &self, + module: &ModuleIdent, + struct_name: &DatatypeName, + ) -> Option> { + let fields = match &self.struct_definition(module, struct_name).fields { + N::StructFields::Defined(_, fields) => Some(fields.ref_map(|_, (ndx, _)| *ndx)), + N::StructFields::Native(_) => None, + }; + fields + } + + /// Indicates if the struct is positional. Returns false on native. + pub fn struct_is_positional(&self, module: &ModuleIdent, struct_name: &DatatypeName) -> bool { + match self.struct_definition(module, struct_name).fields { + N::StructFields::Defined(is_positional, _) => is_positional, + N::StructFields::Native(_) => false, } } - pub fn take_use_funs(self) -> BTreeMap { - self.modules + /// Returns the enum variant names in sorted order. + pub fn enum_variants( + &self, + module: &ModuleIdent, + enum_name: &DatatypeName, + ) -> Vec { + let mut names = self + .enum_definition(module, enum_name) + .variants + .ref_map(|_, vdef| vdef.index) + .clone() .into_iter() - .map(|(mident, minfo)| (mident, minfo.use_funs)) - .collect() + .collect::>(); + names.sort_by(|(_, ndx0), (_, ndx1)| ndx0.cmp(ndx1)); + names.into_iter().map(|(name, _ndx)| name).collect() } - pub fn set_module_syntax_methods( - &mut self, - mident: ModuleIdent, - syntax_methods: SyntaxMethods, - ) { - let syntax_methods_ref = &mut self.modules.get_mut(&mident).unwrap().syntax_methods; - *syntax_methods_ref = syntax_methods; + pub fn enum_variant_fields( + &self, + module: &ModuleIdent, + enum_name: &DatatypeName, + variant_name: &VariantName, + ) -> Option> { + let Some(variant) = self + .enum_definition(module, enum_name) + .variants + .get(variant_name) + else { + return None; + }; + match &variant.fields { + N::VariantFields::Defined(_, fields) => Some(fields.ref_map(|_, (ndx, _)| *ndx)), + N::VariantFields::Empty => Some(UniqueMap::new()), + } + } + + /// Indicates if the enum variant is empty. + pub fn enum_variant_is_empty( + &self, + module: &ModuleIdent, + enum_name: &DatatypeName, + variant_name: &VariantName, + ) -> bool { + let vdef = self + .enum_definition(module, enum_name) + .variants + .get(variant_name) + .expect("ICE should have failed during naming"); + match &vdef.fields { + N::VariantFields::Empty => true, + N::VariantFields::Defined(_, _m) => false, + } + } + + /// Indicates if the enum variant is positional. Returns false on empty or missing. + pub fn enum_variant_is_positional( + &self, + module: &ModuleIdent, + enum_name: &DatatypeName, + variant_name: &VariantName, + ) -> bool { + let vdef = self + .enum_definition(module, enum_name) + .variants + .get(variant_name) + .expect("ICE should have failed during naming"); + match &vdef.fields { + N::VariantFields::Empty => false, + N::VariantFields::Defined(is_positional, _m) => *is_positional, + } } } diff --git a/external-crates/move/crates/move-compiler/src/typing/core.rs b/external-crates/move/crates/move-compiler/src/typing/core.rs index ccc8ff3d4b0fa..8a040dd59d02d 100644 --- a/external-crates/move/crates/move-compiler/src/typing/core.rs +++ b/external-crates/move/crates/move-compiler/src/typing/core.rs @@ -9,8 +9,7 @@ use crate::{ Diagnostic, }, editions::FeatureGate, - expansion::ast::{AbilitySet, Fields, ModuleIdent, ModuleIdent_, Mutability, Visibility}, - hlir::translate::{MATCH_TEMP_PREFIX_SYMBOL, NEW_NAME_DELIM}, + expansion::ast::{AbilitySet, ModuleIdent, ModuleIdent_, Mutability, Visibility}, ice, naming::ast::{ self as N, BlockLabel, BuiltinTypeName_, Color, DatatypeTypeParameter, EnumDefinition, @@ -23,12 +22,12 @@ use crate::{ shared::{ ide::{IDEAnnotation, IDEInfo}, known_attributes::TestingAttribute, + matching::{new_match_var_name, MatchContext}, program_info::*, string_utils::debug_print, unique_map::UniqueMap, *, }, - typing::match_compilation, FullyCompiledProgram, }; use move_ir_types::location::*; @@ -81,11 +80,7 @@ pub enum MacroExpansion { } pub(super) struct TypingDebugFlags { - pub(super) match_translation: bool, - pub(super) match_specialization: bool, pub(super) match_counterexample: bool, - pub(super) match_work_queue: bool, - pub(super) match_constant_conversion: bool, pub(super) autocomplete_resolution: bool, pub(super) function_translation: bool, pub(super) type_elaboration: bool, @@ -186,11 +181,7 @@ impl<'env> Context<'env> { ) -> Self { let global_use_funs = UseFunsScope::global(&info); let debug = TypingDebugFlags { - match_translation: false, - match_specialization: false, match_counterexample: false, - match_work_queue: false, - match_constant_conversion: false, autocomplete_resolution: false, function_translation: false, type_elaboration: false, @@ -720,178 +711,6 @@ impl<'env> Context<'env> { *max_variable_color = color; } - //******************************************** - // Match Compilation Helpers - //******************************************** - - pub fn is_struct(&self, module: &ModuleIdent, datatype_name: &DatatypeName) -> bool { - matches!( - self.datatype_kind(module, datatype_name), - DatatypeKind::Struct - ) - } - - pub fn struct_fields( - &self, - module: &ModuleIdent, - struct_name: &DatatypeName, - ) -> Option> { - let fields = match &self.struct_definition(module, struct_name).fields { - N::StructFields::Defined(_, fields) => Some(fields.ref_map(|_, (ndx, _)| *ndx)), - N::StructFields::Native(_) => None, - }; - assert!(fields.is_some() || self.env.has_errors()); - fields - } - - /// Indicates if the struct is positional. Returns false on native. - pub fn struct_is_positional(&self, module: &ModuleIdent, struct_name: &DatatypeName) -> bool { - match self.modules.struct_definition(module, struct_name).fields { - N::StructFields::Defined(is_positional, _) => is_positional, - N::StructFields::Native(_) => false, - } - } - - /// Returns the enum variant names in sorted order. - pub fn enum_variants( - &self, - module: &ModuleIdent, - enum_name: &DatatypeName, - ) -> Vec { - let mut names = self - .enum_definition(module, enum_name) - .variants - .ref_map(|_, vdef| vdef.index) - .clone() - .into_iter() - .collect::>(); - names.sort_by(|(_, ndx0), (_, ndx1)| ndx0.cmp(ndx1)); - names.into_iter().map(|(name, _ndx)| name).collect() - } - - pub fn enum_variant_fields( - &self, - module: &ModuleIdent, - enum_name: &DatatypeName, - variant_name: &VariantName, - ) -> Option> { - let Some(variant) = self - .enum_definition(module, enum_name) - .variants - .get(variant_name) - else { - assert!(self.env.has_errors()); - return None; - }; - match &variant.fields { - N::VariantFields::Defined(_, fields) => Some(fields.ref_map(|_, (ndx, _)| *ndx)), - N::VariantFields::Empty => Some(UniqueMap::new()), - } - } - - /// Indicates if the enum variant is empty. - pub fn enum_variant_is_empty( - &self, - module: &ModuleIdent, - enum_name: &DatatypeName, - variant_name: &VariantName, - ) -> bool { - let vdef = self - .enum_definition(module, enum_name) - .variants - .get(variant_name) - .expect("ICE should have failed during naming"); - match &vdef.fields { - N::VariantFields::Empty => true, - N::VariantFields::Defined(_, _m) => false, - } - } - - /// Indicates if the enum variant is positional. Returns false on empty or missing. - pub fn enum_variant_is_positional( - &self, - module: &ModuleIdent, - enum_name: &DatatypeName, - variant_name: &VariantName, - ) -> bool { - let vdef = self - .enum_definition(module, enum_name) - .variants - .get(variant_name) - .expect("ICE should have failed during naming"); - match &vdef.fields { - N::VariantFields::Empty => false, - N::VariantFields::Defined(is_positional, _m) => *is_positional, - } - } - - pub fn make_imm_ref_match_binders( - &mut self, - pattern_loc: Loc, - arg_types: Fields, - ) -> Vec<(Field, N::Var, N::Type)> { - fn make_imm_ref_ty(ty: N::Type) -> N::Type { - match ty { - sp!(_, N::Type_::Ref(false, _)) => ty, - sp!(loc, N::Type_::Ref(true, inner)) => sp(loc, N::Type_::Ref(false, inner)), - ty => { - let loc = ty.loc; - sp(loc, N::Type_::Ref(false, Box::new(ty))) - } - } - } - - let fields = match_compilation::order_fields_by_decl(None, arg_types.clone()); - fields - .into_iter() - .map(|(_, field_name, field_type)| { - ( - field_name, - self.new_match_var(field_name.to_string(), pattern_loc), - make_imm_ref_ty(field_type), - ) - }) - .collect::>() - } - - pub fn make_unpack_binders( - &mut self, - pattern_loc: Loc, - arg_types: Fields, - ) -> Vec<(Field, N::Var, N::Type)> { - let fields = match_compilation::order_fields_by_decl(None, arg_types.clone()); - fields - .into_iter() - .map(|(_, field_name, field_type)| { - ( - field_name, - self.new_match_var(field_name.to_string(), pattern_loc), - field_type, - ) - }) - .collect::>() - } - - /// Makes a new `naming/ast.rs` variable. Does _not_ record it as a function local, since this - /// should only be called in match expansion, which will have its body processed in HLIR - /// translation after type expansion. - pub fn new_match_var(&mut self, name: String, loc: Loc) -> N::Var { - let id = self.next_match_var_id(); - let name = format!( - "{}{NEW_NAME_DELIM}{name}{NEW_NAME_DELIM}{id}", - *MATCH_TEMP_PREFIX_SYMBOL, - ) - .into(); - sp( - loc, - N::Var_ { - name, - id: id as u16, - color: 1, - }, - ) - } - fn next_match_var_id(&mut self) -> usize { self.next_match_var_id += 1; self.next_match_var_id @@ -963,6 +782,38 @@ impl<'env> Context<'env> { } } +impl MatchContext for Context<'_> { + fn env(&mut self) -> &mut CompilationEnv { + self.env + } + + fn env_ref(&self) -> &CompilationEnv { + self.env + } + + /// Makes a new `naming/ast.rs` variable. Does _not_ record it as a function local, since this + /// should only be called in match expansion, which will have its body processed in HLIR + /// translation after type expansion. + fn new_match_var(&mut self, name: String, loc: Loc) -> N::Var { + let id = self.next_match_var_id(); + let name = new_match_var_name(&name, id); + // NOTE: Since these variables are only used for counterexample generation, etc., color + // does not matter. + sp( + loc, + N::Var_ { + name, + id: id as u16, + color: 0, + }, + ) + } + + fn program_info(&self) -> &ProgramInfo { + &self.modules + } +} + //************************************************************************************************** // Subst //************************************************************************************************** diff --git a/external-crates/move/crates/move-compiler/src/typing/dependency_ordering.rs b/external-crates/move/crates/move-compiler/src/typing/dependency_ordering.rs index fcb9d302ba8dc..c87c12b0718b1 100644 --- a/external-crates/move/crates/move-compiler/src/typing/dependency_ordering.rs +++ b/external-crates/move/crates/move-compiler/src/typing/dependency_ordering.rs @@ -359,16 +359,19 @@ fn lvalue(context: &mut Context, sp!(loc, lv_): &T::LValue) { match lv_ { L::Ignore => (), L::Var { ty, .. } => type_(context, ty), - L::Unpack(m, _, tys, fields) - | L::BorrowUnpack(_, m, _, tys, fields) - | L::UnpackVariant(m, _, _, tys, fields) - | L::BorrowUnpackVariant(_, m, _, _, tys, fields) => { + L::Unpack(m, _, tys, fields) | L::BorrowUnpack(_, m, _, tys, fields) => { context.add_usage(*m, *loc); types(context, tys); for (_, _, (_, (_, field))) in fields { lvalue(context, field) } } + L::BorrowUnpackVariant(..) | L::UnpackVariant(..) => { + context.env.add_diag(ice!(( + *loc, + "variant unpacking shouldn't occur before match expansion" + ))); + } } } @@ -399,19 +402,22 @@ fn exp(context: &mut Context, e: &T::Exp) { exp(context, e2); exp(context, e3); } - E::Match(_subject, _arms) => { + E::Match(esubject, arms) => { + exp(context, esubject); + for sp!(_, arm) in &arms.value { + pat(context, &arm.pattern); + if let Some(guard) = arm.guard.as_ref() { + exp(context, guard) + } + exp(context, &arm.rhs); + } + } + E::VariantMatch(..) => { context.env.add_diag(ice!(( e.exp.loc, - "shouldn't find match after match compilation step" + "shouldn't find variant match before HLIR lowering" ))); } - E::VariantMatch(subject, (module, _), arms) => { - exp(context, subject); - context.add_usage(*module, e.exp.loc); - for (_, rhs) in arms { - exp(context, rhs); - } - } E::While(_, e1, e2) => { exp(context, e1); exp(context, e2); @@ -483,3 +489,26 @@ fn exp(context: &mut Context, e: &T::Exp) { | E::UnresolvedError => (), } } + +#[growing_stack] +fn pat(context: &mut Context, p: &T::MatchPattern) { + use T::UnannotatedPat_ as P; + match &p.pat.value { + P::Variant(m, _, _, tys, fields) + | P::BorrowVariant(_, m, _, _, tys, fields) + | P::Struct(m, _, tys, fields) + | P::BorrowStruct(_, m, _, tys, fields) => { + context.add_usage(*m, p.pat.loc); + types(context, tys); + for (_, _, (_, (_, p))) in fields { + pat(context, p) + } + } + P::At(_, inner) => pat(context, inner), + P::Or(lhs, rhs) => { + pat(context, lhs); + pat(context, rhs); + } + P::Constant(_, _) | P::Wildcard | P::ErrorPat | P::Binder(_, _) | P::Literal(_) => (), + } +} diff --git a/external-crates/move/crates/move-compiler/src/typing/match_analysis.rs b/external-crates/move/crates/move-compiler/src/typing/match_analysis.rs new file mode 100644 index 0000000000000..bb314da2dfc59 --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/typing/match_analysis.rs @@ -0,0 +1,778 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + diag, + expansion::ast::{ModuleIdent, Value_}, + ice, + naming::ast::BuiltinTypeName_, + parser::ast::{DatatypeName, VariantName}, + shared::{ + ast_debug::AstDebug, + ide::{IDEAnnotation, MissingMatchArmsInfo, PatternSuggestion}, + matching::{MatchContext, PatternMatrix}, + string_utils::{debug_print, format_oxford_list}, + Identifier, + }, + typing::{ + ast as T, + core::{error_format, Context, Subst}, + visitor::TypingVisitorContext, + }, +}; +use move_ir_types::location::*; +use move_proc_macros::growing_stack; +use std::{ + collections::{BTreeSet, VecDeque}, + fmt::Display, +}; + +//************************************************************************************************** +// Description +//************************************************************************************************** +// This visitor performs two match analysis steps: +// 1. If IDE mode is enabled, report all missing top-level arms as IDE information. +// 2. Ensure the match is exhaustive, or replace it with an error if it is not. + +//************************************************************************************************** +// Entry and Visitor +//************************************************************************************************** + +struct MatchCompiler<'ctx, 'env> { + context: &'ctx mut Context<'env>, +} + +impl TypingVisitorContext for MatchCompiler<'_, '_> { + fn visit_exp_custom(&mut self, exp: &mut T::Exp) -> bool { + use T::UnannotatedExp_ as E; + if let E::Match(subject, arms) = &exp.exp.value { + debug_print!(self.context.debug.match_counterexample, + ("subject" => subject), + (lines "arms" => &arms.value) + ); + if invalid_match(self.context, subject, arms) { + debug_print!( + self.context.debug.match_counterexample, + (msg "counterexample found") + ); + let err_exp = T::exp( + exp.ty.clone(), + sp(subject.exp.loc, T::UnannotatedExp_::UnresolvedError), + ); + let _ = std::mem::replace(exp, err_exp); + true + } else { + false + } + } else { + false + } + } + + fn add_warning_filter_scope(&mut self, filter: crate::diagnostics::WarningFilters) { + self.context.env.add_warning_filter_scope(filter); + } + + fn pop_warning_filter_scope(&mut self) { + self.context.env.pop_warning_filter_scope(); + } +} + +pub fn function_body_(context: &mut Context, b_: &mut T::FunctionBody_) { + match b_ { + T::FunctionBody_::Native | T::FunctionBody_::Macro => (), + T::FunctionBody_::Defined(es) => { + let mut compiler = MatchCompiler { context }; + compiler.visit_seq(es); + } + } +} + +/// Check a match, generating a counterexample if one exists. Also reports IDE arm suggestions as +/// IDE information. If this returns `true`, the match is invalid and should be replaced with an +/// error. +fn invalid_match( + context: &mut Context, + subject: &T::Exp, + arms: &Spanned>, +) -> bool { + let arms_loc = arms.loc; + let (pattern_matrix, _arms) = + PatternMatrix::from(context, subject.ty.clone(), arms.value.clone()); + + let mut counterexample_matrix = pattern_matrix.clone(); + let has_guards = counterexample_matrix.has_guards(); + counterexample_matrix.remove_guarded_arms(); + if context.env.ide_mode() { + // Do this first, as it's a borrow and a shallow walk. + ide_report_missing_arms(context, arms_loc, &counterexample_matrix); + } + find_counterexample(context, subject.exp.loc, counterexample_matrix, has_guards) +} + +//------------------------------------------------ +// Counterexample Generation +//------------------------------------------------ + +#[derive(Clone, Debug)] +enum CounterExample { + Wildcard, + Literal(String), + Struct( + DatatypeName, + /* is_positional */ bool, + Vec<(String, CounterExample)>, + ), + Variant( + DatatypeName, + VariantName, + /* is_positional */ bool, + Vec<(String, CounterExample)>, + ), + Note(String, Box), +} + +impl CounterExample { + fn into_notes(self) -> VecDeque { + match self { + CounterExample::Wildcard => VecDeque::new(), + CounterExample::Literal(_) => VecDeque::new(), + CounterExample::Note(s, next) => { + let mut notes = next.into_notes(); + notes.push_front(s.clone()); + notes + } + CounterExample::Variant(_, _, _, inner) => inner + .into_iter() + .flat_map(|(_, ce)| ce.into_notes()) + .collect::>(), + CounterExample::Struct(_, _, inner) => inner + .into_iter() + .flat_map(|(_, ce)| ce.into_notes()) + .collect::>(), + } + } +} + +impl Display for CounterExample { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CounterExample::Wildcard => write!(f, "_"), + CounterExample::Literal(s) => write!(f, "{}", s), + CounterExample::Note(_, inner) => inner.fmt(f), + CounterExample::Struct(s, is_positional, args) => { + write!(f, "{}", s)?; + if *is_positional { + write!(f, "(")?; + write!( + f, + "{}", + args.iter() + .map(|(_name, arg)| { format!("{}", arg) }) + .collect::>() + .join(", ") + )?; + write!(f, ")") + } else { + write!(f, " {{ ")?; + write!( + f, + "{}", + args.iter() + .map(|(name, arg)| { format!("{}: {}", name, arg) }) + .collect::>() + .join(", ") + )?; + write!(f, " }}") + } + } + CounterExample::Variant(e, v, is_positional, args) => { + write!(f, "{}::{}", e, v)?; + if !args.is_empty() { + if *is_positional { + write!(f, "(")?; + write!( + f, + "{}", + args.iter() + .map(|(_name, arg)| { format!("{}", arg) }) + .collect::>() + .join(", ") + )?; + write!(f, ")") + } else { + write!(f, " {{ ")?; + write!( + f, + "{}", + args.iter() + .map(|(name, arg)| { format!("{}: {}", name, arg) }) + .collect::>() + .join(", ") + )?; + write!(f, " }}") + } + } else { + Ok(()) + } + } + } + } +} + +/// Returns true if it found a counter-example. Assumes all arms with guards have been removed from +/// the provided matrix. +fn find_counterexample( + context: &mut Context, + loc: Loc, + matrix: PatternMatrix, + has_guards: bool, +) -> bool { + // If the matrix is only errors (or empty), it was all error or something else (like typing) + // went wrong; no counterexample is required. + if !matrix.is_empty() && !matrix.patterns_empty() && matrix.all_errors() { + debug_print!(context.debug.match_counterexample, (msg "errors"), ("matrix" => matrix; dbg)); + assert!(context.env.has_errors()); + return true; + } + find_counterexample_impl(context, loc, matrix, has_guards) +} + +/// Returns true if it found a counter-example. +fn find_counterexample_impl( + context: &mut Context, + loc: Loc, + matrix: PatternMatrix, + has_guards: bool, +) -> bool { + fn make_wildcards(n: usize) -> Vec { + std::iter::repeat(CounterExample::Wildcard) + .take(n) + .collect() + } + + #[growing_stack] + fn counterexample_bool( + context: &mut Context, + matrix: PatternMatrix, + arity: u32, + ndx: &mut u32, + ) -> Option> { + let literals = matrix.first_lits(); + assert!(literals.len() <= 2, "ICE match exhaustiveness failure"); + if literals.len() == 2 { + // Saturated + for lit in literals { + if let Some(counterexample) = + counterexample_rec(context, matrix.specialize_literal(&lit).1, arity - 1, ndx) + { + let lit_str = format!("{}", lit); + let result = [CounterExample::Literal(lit_str)] + .into_iter() + .chain(counterexample) + .collect(); + return Some(result); + } + } + None + } else { + let (_, default) = matrix.specialize_default(); + if let Some(counterexample) = counterexample_rec(context, default, arity - 1, ndx) { + if literals.is_empty() { + let result = [CounterExample::Wildcard] + .into_iter() + .chain(counterexample) + .collect(); + Some(result) + } else { + let mut unused = BTreeSet::from([Value_::Bool(true), Value_::Bool(false)]); + for lit in literals { + unused.remove(&lit.value); + } + let result = [CounterExample::Literal(format!( + "{}", + unused.first().unwrap() + ))] + .into_iter() + .chain(counterexample) + .collect(); + Some(result) + } + } else { + None + } + } + } + + #[growing_stack] + fn counterexample_builtin( + context: &mut Context, + matrix: PatternMatrix, + arity: u32, + ndx: &mut u32, + ) -> Option> { + // For all other non-literals, we don't consider a case where the constructors are + // saturated. + let literals = matrix.first_lits(); + let (_, default) = matrix.specialize_default(); + if let Some(counterexample) = counterexample_rec(context, default, arity - 1, ndx) { + if literals.is_empty() { + let result = [CounterExample::Wildcard] + .into_iter() + .chain(counterexample) + .collect(); + Some(result) + } else { + let n_id = format!("_{}", ndx); + *ndx += 1; + let lit_str = { + let lit_len = literals.len() as u64; + let fmt_lits = if lit_len > 4 { + let mut result = literals + .into_iter() + .take(3) + .map(|lit| lit.to_string()) + .collect::>(); + result.push(format!("{} other values", lit_len - 3)); + result + } else { + literals + .into_iter() + .map(|lit| lit.to_string()) + .collect::>() + }; + format_oxford_list!("or", "{}", fmt_lits) + }; + let lit_msg = format!("When '{}' is not {}", n_id, lit_str); + let lit_ce = CounterExample::Note(lit_msg, Box::new(CounterExample::Literal(n_id))); + let result = [lit_ce].into_iter().chain(counterexample).collect(); + Some(result) + } + } else { + None + } + } + + #[growing_stack] + fn counterexample_datatype( + context: &mut Context, + matrix: PatternMatrix, + arity: u32, + ndx: &mut u32, + mident: ModuleIdent, + datatype_name: DatatypeName, + ) -> Option> { + debug_print!( + context.debug.match_counterexample, + (lines "matrix types" => &matrix.tys; verbose) + ); + if context.modules.is_struct(&mident, &datatype_name) { + // For a struct, we only care if we destructure it. If we do, we want to specialize and + // recur. If we don't, we check it as a default specialization. + if let Some((ploc, arg_types)) = matrix.first_struct_ctors() { + let ctor_arity = arg_types.len() as u32; + let fringe_binders = context.make_imm_ref_match_binders(ploc, arg_types); + let is_positional = context + .modules + .struct_is_positional(&mident, &datatype_name); + let names = fringe_binders + .iter() + .map(|(name, _, _)| name.to_string()) + .collect::>(); + let bind_tys = fringe_binders + .iter() + .map(|(_, _, ty)| ty) + .collect::>(); + let (_, inner_matrix) = matrix.specialize_struct(context, bind_tys); + if let Some(mut counterexample) = + counterexample_rec(context, inner_matrix, ctor_arity + arity - 1, ndx) + { + let ctor_args = counterexample + .drain(0..(ctor_arity as usize)) + .collect::>(); + assert!(ctor_args.len() == names.len()); + let output = [CounterExample::Struct( + datatype_name, + is_positional, + names.into_iter().zip(ctor_args).collect::>(), + )] + .into_iter() + .chain(counterexample) + .collect(); + Some(output) + } else { + // If we didn't find a counterexample in the destructuring cases, we're done. + None + } + } else { + let (_, default) = matrix.specialize_default(); + // `_` is a reasonable counterexample since we never unpacked this struct + if let Some(counterexample) = counterexample_rec(context, default, arity - 1, ndx) { + // If we didn't match any head constructor, `_` is a reasonable + // counter-example entry. + let mut result = vec![CounterExample::Wildcard]; + result.extend(&mut counterexample.into_iter()); + Some(result) + } else { + None + } + } + } else { + let mut unmatched_variants = context + .modules + .enum_variants(&mident, &datatype_name) + .into_iter() + .collect::>(); + + let ctors = matrix.first_variant_ctors(); + for ctor in ctors.keys() { + unmatched_variants.remove(ctor); + } + if unmatched_variants.is_empty() { + for (ctor, (ploc, arg_types)) in ctors { + let ctor_arity = arg_types.len() as u32; + let fringe_binders = context.make_imm_ref_match_binders(ploc, arg_types); + let is_positional = + context + .modules + .enum_variant_is_positional(&mident, &datatype_name, &ctor); + let names = fringe_binders + .iter() + .map(|(name, _, _)| name.to_string()) + .collect::>(); + let bind_tys = fringe_binders + .iter() + .map(|(_, _, ty)| ty) + .collect::>(); + let (_, inner_matrix) = matrix.specialize_variant(context, &ctor, bind_tys); + if let Some(mut counterexample) = + counterexample_rec(context, inner_matrix, ctor_arity + arity - 1, ndx) + { + let ctor_args = counterexample + .drain(0..(ctor_arity as usize)) + .collect::>(); + assert!(ctor_args.len() == names.len()); + let output = [CounterExample::Variant( + datatype_name, + ctor, + is_positional, + names + .into_iter() + .zip(ctor_args.into_iter()) + .collect::>(), + )] + .into_iter() + .chain(counterexample) + .collect(); + return Some(output); + } + } + None + } else { + let (_, default) = matrix.specialize_default(); + if let Some(counterexample) = counterexample_rec(context, default, arity - 1, ndx) { + if ctors.is_empty() { + // If we didn't match any head constructor, `_` is a reasonable + // counter-example entry. + let mut result = vec![CounterExample::Wildcard]; + result.extend(&mut counterexample.into_iter()); + Some(result) + } else { + let variant_name = unmatched_variants.first().unwrap(); + let is_positional = context.modules.enum_variant_is_positional( + &mident, + &datatype_name, + variant_name, + ); + let ctor_args = context + .modules + .enum_variant_fields(&mident, &datatype_name, variant_name) + .unwrap(); + let names = ctor_args + .iter() + .map(|(_, field, _)| field.to_string()) + .collect::>(); + let ctor_arity = names.len(); + let result = [CounterExample::Variant( + datatype_name, + *variant_name, + is_positional, + names.into_iter().zip(make_wildcards(ctor_arity)).collect(), + )] + .into_iter() + .chain(counterexample) + .collect(); + Some(result) + } + } else { + // If we are missing a variant but everything else is fine, we're done. + None + } + } + } + } + + // \mathcal{I} from Maranget. Warning for pattern matching. 1992. + #[growing_stack] + fn counterexample_rec( + context: &mut Context, + matrix: PatternMatrix, + arity: u32, + ndx: &mut u32, + ) -> Option> { + debug_print!(context.debug.match_counterexample, ("checking matrix" => matrix; verbose)); + let result = if matrix.patterns_empty() { + None + } else if let Some(ty) = matrix.tys.first() { + if let Some(sp!(_, BuiltinTypeName_::Bool)) = ty.value.unfold_to_builtin_type_name() { + counterexample_bool(context, matrix, arity, ndx) + } else if let Some(_builtin) = ty.value.unfold_to_builtin_type_name() { + counterexample_builtin(context, matrix, arity, ndx) + } else if let Some((mident, datatype_name)) = ty + .value + .unfold_to_type_name() + .and_then(|sp!(_, name)| name.datatype_name()) + { + counterexample_datatype(context, matrix, arity, ndx, mident, datatype_name) + } else { + // This can only be a binding or wildcard, so we act accordingly. + let (_, default) = matrix.specialize_default(); + if let Some(counterexample) = counterexample_rec(context, default, arity - 1, ndx) { + let result = [CounterExample::Wildcard] + .into_iter() + .chain(counterexample) + .collect(); + Some(result) + } else { + None + } + } + } else { + assert!(matrix.is_empty()); + Some(make_wildcards(arity as usize)) + }; + debug_print!(context.debug.match_counterexample, (opt "result" => &result; sdbg)); + result + } + + let mut ndx = 0; + + if let Some(mut counterexample) = counterexample_rec(context, matrix, 1, &mut ndx) { + debug_print!( + context.debug.match_counterexample, + ("counterexamples #" => counterexample.len(); fmt), + (lines "counterexamples" => &counterexample; fmt) + ); + assert!(counterexample.len() == 1); + let counterexample = counterexample.remove(0); + let msg = format!("Pattern '{}' not covered", counterexample); + let mut diag = diag!(TypeSafety::IncompletePattern, (loc, msg)); + for note in counterexample.into_notes() { + diag.add_note(note); + } + if has_guards { + diag.add_note("Match arms with guards are not considered for coverage."); + } + context.env.add_diag(diag); + true + } else { + false + } +} + +//------------------------------------------------ +// IDE Arm Suggestion Generation +//------------------------------------------------ + +/// Produces IDE information if the top-level match is incomplete. Assumes all arms with guards +/// have been removed from the provided matrix. +fn ide_report_missing_arms(context: &mut Context, loc: Loc, matrix: &PatternMatrix) { + use PatternSuggestion as PS; + // This function looks at the very top-level of the match. For any arm missing, it suggests the + // IDE add an arm to address that missing one. + + fn report_bool(context: &mut Context, loc: Loc, matrix: &PatternMatrix) { + let literals = matrix.first_lits(); + assert!(literals.len() <= 2, "ICE match exhaustiveness failure"); + // Figure out which are missing + let mut unused = BTreeSet::from([Value_::Bool(true), Value_::Bool(false)]); + for lit in literals { + unused.remove(&lit.value); + } + if !unused.is_empty() { + let arms = unused.into_iter().map(PS::Value).collect::>(); + let info = MissingMatchArmsInfo { arms }; + context + .env + .add_ide_annotation(loc, IDEAnnotation::MissingMatchArms(Box::new(info))); + } + } + + fn report_builtin(context: &mut Context, loc: Loc, matrix: &PatternMatrix) { + // For all other non-literals, we don't consider a case where the constructors are + // saturated. If it doesn't have a wildcard, we suggest adding a wildcard. + if !matrix.has_default_arm() { + let info = MissingMatchArmsInfo { + arms: vec![PS::Wildcard], + }; + context + .env + .add_ide_annotation(loc, IDEAnnotation::MissingMatchArms(Box::new(info))); + } + } + + fn report_datatype( + context: &mut Context, + loc: Loc, + matrix: &PatternMatrix, + mident: ModuleIdent, + name: DatatypeName, + ) { + if context.modules.is_struct(&mident, &name) { + if !matrix.is_empty() { + // If the matrix isn't empty, we _must_ have matched the struct with at least one + // non-guard arm (either wildcards or the struct itself), so we're fine. + return; + } + // If the matrix _is_ empty, we suggest adding an unpack. + let is_positional = context.modules.struct_is_positional(&mident, &name); + let Some(fields) = context.modules.struct_fields(&mident, &name) else { + context.env.add_diag(ice!(( + loc, + "Tried to look up fields for this struct and found none" + ))); + return; + }; + // NB: We might not have a concrete type for the type parameters to the datatype (due + // to type errors or otherwise), so we use stand-in types. Since this is IDE + // information that should be inserted and then re-compiled, this should work for our + // purposes. + + let suggestion = if is_positional { + PS::UnpackPositionalStruct { + module: mident, + name, + field_count: fields.len(), + } + } else { + PS::UnpackNamedStruct { + module: mident, + name, + fields: fields.into_iter().map(|(field, _)| field.value()).collect(), + } + }; + let info = MissingMatchArmsInfo { + arms: vec![suggestion], + }; + context + .env + .add_ide_annotation(loc, IDEAnnotation::MissingMatchArms(Box::new(info))); + } else { + // If there's a default arm, no suggestion is necessary. + if matrix.has_default_arm() { + return; + } + + let mut unmatched_variants = context + .modules + .enum_variants(&mident, &name) + .into_iter() + .collect::>(); + let ctors = matrix.first_variant_ctors(); + for ctor in ctors.keys() { + unmatched_variants.remove(ctor); + } + // If all of the variants were matched, no suggestion is necessary. + if unmatched_variants.is_empty() { + return; + } + let mut arms = vec![]; + // re-iterate the original so we generate these in definition order + for variant in context.modules.enum_variants(&mident, &name).into_iter() { + if !unmatched_variants.contains(&variant) { + continue; + } + let is_empty = context + .modules + .enum_variant_is_empty(&mident, &name, &variant); + let is_positional = context + .modules + .enum_variant_is_positional(&mident, &name, &variant); + let Some(fields) = context + .modules + .enum_variant_fields(&mident, &name, &variant) + else { + context.env.add_diag(ice!(( + loc, + "Tried to look up fields for this enum and found none" + ))); + continue; + }; + let suggestion = if is_empty { + PS::UnpackEmptyVariant { + module: mident, + enum_name: name, + variant_name: variant, + } + } else if is_positional { + PS::UnpackPositionalVariant { + module: mident, + enum_name: name, + variant_name: variant, + field_count: fields.len(), + } + } else { + PS::UnpackNamedVariant { + module: mident, + enum_name: name, + variant_name: variant, + fields: fields.into_iter().map(|(field, _)| field.value()).collect(), + } + }; + arms.push(suggestion); + } + let info = MissingMatchArmsInfo { arms }; + context + .env + .add_ide_annotation(loc, IDEAnnotation::MissingMatchArms(Box::new(info))); + } + } + + let Some(ty) = matrix.tys.first() else { + context.env.add_diag(ice!(( + loc, + "Pattern matrix with no types handed to IDE function" + ))); + return; + }; + if let Some(sp!(_, BuiltinTypeName_::Bool)) = &ty.value.unfold_to_builtin_type_name() { + report_bool(context, loc, matrix) + } else if let Some(_builtin) = ty.value.unfold_to_builtin_type_name() { + report_builtin(context, loc, matrix) + } else if let Some((mident, datatype_name)) = ty + .value + .unfold_to_type_name() + .and_then(|sp!(_, name)| name.datatype_name()) + { + report_datatype(context, loc, matrix, mident, datatype_name) + } else { + if !context.env.has_errors() { + // It's unclear how we got here, so report an ICE and suggest a wildcard. + context.env.add_diag(ice!(( + loc, + format!( + "Found non-matchable type {} as match subject", + error_format(ty, &Subst::empty()) + ) + ))); + } + if !matrix.has_default_arm() { + let info = MissingMatchArmsInfo { + arms: vec![PS::Wildcard], + }; + context + .env + .add_ide_annotation(loc, IDEAnnotation::MissingMatchArms(Box::new(info))); + } + } +} diff --git a/external-crates/move/crates/move-compiler/src/typing/mod.rs b/external-crates/move/crates/move-compiler/src/typing/mod.rs index 7a1f00255642d..d287b5f59243e 100644 --- a/external-crates/move/crates/move-compiler/src/typing/mod.rs +++ b/external-crates/move/crates/move-compiler/src/typing/mod.rs @@ -8,7 +8,7 @@ mod dependency_ordering; mod expand; mod infinite_instantiations; mod macro_expand; -mod match_compilation; +mod match_analysis; mod recursive_datatypes; mod syntax_methods; pub(crate) mod translate; diff --git a/external-crates/move/crates/move-compiler/src/typing/translate.rs b/external-crates/move/crates/move-compiler/src/typing/translate.rs index 724f959f9665c..fdcb2ae78d9f4 100644 --- a/external-crates/move/crates/move-compiler/src/typing/translate.rs +++ b/external-crates/move/crates/move-compiler/src/typing/translate.rs @@ -34,7 +34,7 @@ use crate::{ core::{ self, public_testing_visibility, Context, PublicForTesting, ResolvedFunctionType, Subst, }, - dependency_ordering, expand, infinite_instantiations, macro_expand, match_compilation, + dependency_ordering, expand, infinite_instantiations, macro_expand, match_analysis, recursive_datatypes, syntax_methods::validate_syntax_methods, }, @@ -349,7 +349,7 @@ fn function_body(context: &mut Context, sp!(loc, nb_): N::FunctionBody) -> T::Fu }; core::solve_constraints(context); expand::function_body_(context, &mut b_); - match_compilation::function_body_(context, &mut b_); + match_analysis::function_body_(context, &mut b_); debug_print!(context.debug.function_translation, ("output" => b_)); sp(loc, b_) }