diff --git a/Cargo.lock b/Cargo.lock index 8e895a74d52..44d081d0d0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3099,6 +3099,7 @@ dependencies = [ "fuel-vm", "generational-arena", "hex", + "itertools", "lazy_static", "nanoid", "petgraph", diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index e96dc7f9953..67144293069 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -20,6 +20,7 @@ fuel-asm = "0.2" fuel-crypto = "0.4.0" fuel-vm = "0.5" hex = { version = "0.4", optional = true } +itertools = "0.10" lazy_static = "1.4" nanoid = "0.4" pest = { version = "3.0.4", package = "fuel-pest" } diff --git a/sway-core/src/asm_generation/from_ir.rs b/sway-core/src/asm_generation/from_ir.rs index 62b41d2bc3c..1066739004c 100644 --- a/sway-core/src/asm_generation/from_ir.rs +++ b/sway-core/src/asm_generation/from_ir.rs @@ -2148,7 +2148,7 @@ mod tests { print_intermediate_asm: false, print_finalized_asm: false, print_ir: false, - generated_names: std::sync::Arc::new(std::sync::Mutex::new(vec![])), + generated_names: Default::default(), }, ); diff --git a/sway-core/src/error.rs b/sway-core/src/error.rs index 2f53c9e68eb..66f658a464d 100644 --- a/sway-core/src/error.rs +++ b/sway-core/src/error.rs @@ -4,6 +4,7 @@ use crate::{ parser::Rule, style::{to_screaming_snake_case, to_snake_case, to_upper_camel_case}, type_engine::*, + VariableDeclaration, }; use sway_types::{ident::Ident, span::Span}; @@ -78,6 +79,25 @@ pub(crate) fn ok( } } +/// Acts as the result of parsing `Declaration`s, `Expression`s, etc. +/// Some `Expression`s need to be able to create `VariableDeclaration`s, +/// so this struct is used to "bubble up" those declarations to a viable +/// place in the AST. +#[derive(Debug, Clone)] +pub struct ParserLifter { + pub var_decls: Vec, + pub value: T, +} + +impl ParserLifter { + pub(crate) fn empty(value: T) -> Self { + ParserLifter { + var_decls: vec![], + value, + } + } +} + #[derive(Debug, Clone)] pub struct CompileResult { pub value: Option, @@ -254,6 +274,7 @@ pub enum Warning { ShadowingReservedRegister { reg_name: Ident, }, + MatchExpressionUnreachableArm, } impl fmt::Display for Warning { @@ -359,6 +380,7 @@ impl fmt::Display for Warning { "This register declaration shadows the reserved register, \"{}\".", reg_name ), + MatchExpressionUnreachableArm => write!(f, "This match arm is unreachable."), } } } @@ -390,8 +412,6 @@ pub enum CompileError { }, #[error("Unimplemented feature: {0}")] Unimplemented(&'static str, Span), - #[error("pattern matching algorithm failure on: {0}")] - PatternMatchingAlgorithmFailure(&'static str, Span), #[error("{0}")] TypeError(TypeError), #[error("Error parsing input: expected {err:?}")] @@ -820,6 +840,11 @@ pub enum CompileError { " )] MatchWrongType { expected: TypeId, span: Span }, + #[error("Non-exhaustive match expression. Missing patterns {missing_patterns}")] + MatchExpressionNonExhaustive { + missing_patterns: String, + span: Span, + }, #[error("Impure function called inside of pure function. Pure functions can only call other pure functions. Try making the surrounding function impure by prepending \"impure\" to the function declaration.")] PureCalledImpure { span: Span }, #[error("Impure function inside of non-contract. Contract storage is only accessible from contracts.")] @@ -1058,8 +1083,8 @@ impl CompileError { ShadowsOtherSymbol { span, .. } => span, StarImportShadowsOtherSymbol { span, .. } => span, MatchWrongType { span, .. } => span, + MatchExpressionNonExhaustive { span, .. } => span, NotAnEnum { span, .. } => span, - PatternMatchingAlgorithmFailure(_, span) => span, PureCalledImpure { span, .. } => span, ImpureInNonContract { span, .. } => span, IntegerTooLarge { span, .. } => span, diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index a636f557b43..cabac22535f 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -590,19 +590,22 @@ fn parse_root_from_pairs( for pair in input { match pair.as_rule() { Rule::non_var_decl => { - let decl = check!( + let span = span::Span { + span: pair.as_span(), + path: path.clone(), + }; + let decls = check!( Declaration::parse_non_var_from_pair(pair.clone(), config), continue, warnings, errors ); - parse_tree.push(AstNode { - content: AstNodeContent::Declaration(decl), - span: span::Span { - span: pair.as_span(), - path: path.clone(), - }, - }); + for decl in decls.into_iter() { + parse_tree.push(AstNode { + content: AstNodeContent::Declaration(decl), + span: span.clone(), + }); + } } Rule::use_statement => { let stmt = check!( diff --git a/sway-core/src/optimize.rs b/sway-core/src/optimize.rs index becd10114e4..a70acfa23b0 100644 --- a/sway-core/src/optimize.rs +++ b/sway-core/src/optimize.rs @@ -2178,7 +2178,7 @@ mod tests { print_intermediate_asm: false, print_finalized_asm: false, print_ir: false, - generated_names: std::sync::Arc::new(std::sync::Mutex::new(vec![])), + generated_names: Default::default(), }; let mut warnings = vec![]; diff --git a/sway-core/src/parse_tree/code_block.rs b/sway-core/src/parse_tree/code_block.rs index 328ae6656fe..00213a78b05 100644 --- a/sway-core/src/parse_tree/code_block.rs +++ b/sway-core/src/parse_tree/code_block.rs @@ -4,18 +4,17 @@ use crate::{ error::*, parse_tree::{Expression, ReturnStatement}, parser::Rule, - span::Span, - AstNode, AstNodeContent, Declaration, + AstNode, AstNodeContent, Declaration, VariableDeclaration, }; -use sway_types::span; +use sway_types::span::Span; use pest::iterators::Pair; #[derive(Debug, Clone)] pub struct CodeBlock { pub contents: Vec, - pub(crate) whole_block_span: span::Span, + pub(crate) whole_block_span: Span, } impl CodeBlock { @@ -29,13 +28,17 @@ impl CodeBlock { let path = config.map(|c| c.path()); let mut warnings = Vec::new(); let mut errors = Vec::new(); - let whole_block_span = span::Span { + let whole_block_span = Span { span: block.as_span(), path: path.clone(), }; let block_inner = block.into_inner(); let mut contents = Vec::new(); for pair in block_inner { + let span = Span { + span: pair.as_span(), + path: path.clone(), + }; let mut ast_nodes = match pair.as_rule() { Rule::declaration => check!( Declaration::parse_from_pair(pair.clone(), config), @@ -46,14 +49,14 @@ impl CodeBlock { .into_iter() .map(|content| AstNode { content: AstNodeContent::Declaration(content), - span: span::Span { + span: Span { span: pair.as_span(), path: path.clone(), }, }) .collect::>(), Rule::expr_statement => { - let evaluated_node = check!( + let ParserLifter { value, var_decls } = check!( Expression::parse_from_pair( pair.clone().into_inner().next().unwrap().clone(), config @@ -62,61 +65,61 @@ impl CodeBlock { warnings, errors ); - vec![AstNode { - content: AstNodeContent::Expression(evaluated_node), - span: span::Span { - span: pair.as_span(), - path: path.clone(), - }, - }] + let mut ast_node_contents = collect_var_decls(var_decls, span.clone()); + ast_node_contents.push(AstNode { + content: AstNodeContent::Expression(value), + span, + }); + ast_node_contents } Rule::return_statement => { - let evaluated_node = check!( + let ParserLifter { value, var_decls } = check!( ReturnStatement::parse_from_pair(pair.clone(), config), continue, warnings, errors ); - vec![AstNode { - content: AstNodeContent::ReturnStatement(evaluated_node), - span: span::Span { - span: pair.as_span(), - path: path.clone(), - }, - }] + let mut ast_node_contents = collect_var_decls(var_decls, span.clone()); + ast_node_contents.push(AstNode { + content: AstNodeContent::ReturnStatement(value), + span, + }); + ast_node_contents } Rule::expr => { - let res = check!( + let ParserLifter { value, var_decls } = check!( Expression::parse_from_pair(pair.clone(), config), continue, warnings, errors ); - vec![AstNode { - content: AstNodeContent::ImplicitReturnExpression(res.clone()), - span: res.span(), - }] + let mut ast_node_contents = collect_var_decls(var_decls, span.clone()); + let expr_span = value.span(); + ast_node_contents.push(AstNode { + content: AstNodeContent::ImplicitReturnExpression(value), + span: expr_span, + }); + ast_node_contents } Rule::while_loop => { - let res = check!( + let ParserLifter { value, var_decls } = check!( WhileLoop::parse_from_pair(pair.clone(), config), continue, warnings, errors ); - vec![AstNode { - content: AstNodeContent::WhileLoop(res), - span: span::Span { - span: pair.as_span(), - path: path.clone(), - }, - }] + let mut ast_node_contents = collect_var_decls(var_decls, span.clone()); + ast_node_contents.push(AstNode { + content: AstNodeContent::WhileLoop(value), + span, + }); + ast_node_contents } a => { println!("In code block parsing: {:?} {:?}", a, pair.as_str()); errors.push(CompileError::UnimplementedRule( a, - span::Span { + Span { span: pair.as_span(), path: path.clone(), }, @@ -137,3 +140,13 @@ impl CodeBlock { ) } } + +fn collect_var_decls(var_decls: Vec, span: Span) -> Vec { + var_decls + .into_iter() + .map(|x| AstNode { + content: AstNodeContent::Declaration(Declaration::VariableDeclaration(x)), + span: span.clone(), + }) + .collect::>() +} diff --git a/sway-core/src/parse_tree/declaration.rs b/sway-core/src/parse_tree/declaration.rs index a540cbacde4..a0d12704778 100644 --- a/sway-core/src/parse_tree/declaration.rs +++ b/sway-core/src/parse_tree/declaration.rs @@ -44,7 +44,7 @@ impl Declaration { pub(crate) fn parse_non_var_from_pair( decl: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let mut warnings = Vec::new(); let mut errors = Vec::new(); let mut pair = decl.into_inner(); @@ -63,14 +63,14 @@ impl Declaration { warnings, errors ); - Declaration::FunctionDeclaration(fn_decl) + vec![Declaration::FunctionDeclaration(fn_decl)] } - Rule::trait_decl => Declaration::TraitDeclaration(check!( + Rule::trait_decl => vec![Declaration::TraitDeclaration(check!( TraitDeclaration::parse_from_pair(decl_inner, config,), return err(warnings, errors), warnings, errors - )), + ))], Rule::struct_decl => { let struct_decl = check!( StructDeclaration::parse_from_pair(decl_inner, config,), @@ -78,7 +78,7 @@ impl Declaration { warnings, errors ); - Declaration::StructDeclaration(struct_decl) + vec![Declaration::StructDeclaration(struct_decl)] } Rule::enum_decl => { let enum_decl = check!( @@ -87,20 +87,20 @@ impl Declaration { warnings, errors ); - Declaration::EnumDeclaration(enum_decl) + vec![Declaration::EnumDeclaration(enum_decl)] } - Rule::impl_trait => Declaration::ImplTrait(check!( + Rule::impl_trait => vec![Declaration::ImplTrait(check!( ImplTrait::parse_from_pair(decl_inner, config,), return err(warnings, errors), warnings, errors - )), - Rule::impl_self => Declaration::ImplSelf(check!( + ))], + Rule::impl_self => vec![Declaration::ImplSelf(check!( ImplSelf::parse_from_pair(decl_inner, config,), return err(warnings, errors), warnings, errors - )), + ))], Rule::abi_decl => { let abi_decl = check!( AbiDeclaration::parse_from_pair(decl_inner, config,), @@ -108,20 +108,38 @@ impl Declaration { warnings, errors ); - Declaration::AbiDeclaration(abi_decl) + vec![Declaration::AbiDeclaration(abi_decl)] + } + Rule::const_decl => { + let res_result = check!( + ConstantDeclaration::parse_from_pair(decl_inner, config,), + return err(warnings, errors), + warnings, + errors + ); + let mut decls = res_result + .var_decls + .into_iter() + .map(Declaration::VariableDeclaration) + .collect::>(); + decls.push(Declaration::ConstantDeclaration(res_result.value)); + decls + } + Rule::storage_decl => { + let res_result = check!( + StorageDeclaration::parse_from_pair(decl_inner, config), + return err(warnings, errors), + warnings, + errors + ); + let mut decls = res_result + .var_decls + .into_iter() + .map(Declaration::VariableDeclaration) + .collect::>(); + decls.push(Declaration::StorageDeclaration(res_result.value)); + decls } - Rule::const_decl => Declaration::ConstantDeclaration(check!( - ConstantDeclaration::parse_from_pair(decl_inner, config,), - return err(warnings, errors), - warnings, - errors - )), - Rule::storage_decl => Declaration::StorageDeclaration(check!( - StorageDeclaration::parse_from_pair(decl_inner, config), - return err(warnings, errors), - warnings, - errors - )), a => unreachable!("declarations don't have any other sub-types: {:?}", a), }; ok(parsed_declaration, warnings, errors) @@ -168,11 +186,10 @@ impl Declaration { warnings, errors ); - let mut decls = vec![]; - for var_decl in var_decls.into_iter() { - decls.push(Declaration::VariableDeclaration(var_decl)); - } - decls + var_decls + .into_iter() + .map(Declaration::VariableDeclaration) + .collect::>() } Rule::trait_decl => vec![Declaration::TraitDeclaration(check!( TraitDeclaration::parse_from_pair(decl_inner, config), @@ -186,24 +203,33 @@ impl Declaration { warnings, errors ))], - Rule::non_var_decl => vec![check!( + Rule::non_var_decl => check!( Self::parse_non_var_from_pair(decl_inner, config), return err(warnings, errors), warnings, errors - )], + ), Rule::enum_decl => vec![Declaration::EnumDeclaration(check!( EnumDeclaration::parse_from_pair(decl_inner, config), return err(warnings, errors), warnings, errors ))], - Rule::reassignment => vec![Declaration::Reassignment(check!( - Reassignment::parse_from_pair(decl_inner, config), - return err(warnings, errors), - warnings, - errors - ))], + Rule::reassignment => { + let res_result = check!( + Reassignment::parse_from_pair(decl_inner, config), + return err(warnings, errors), + warnings, + errors + ); + let mut decls = res_result + .var_decls + .into_iter() + .map(Declaration::VariableDeclaration) + .collect::>(); + decls.push(Declaration::Reassignment(res_result.value)); + decls + } Rule::impl_trait => vec![Declaration::ImplTrait(check!( ImplTrait::parse_from_pair(decl_inner, config), return err(warnings, errors), @@ -216,12 +242,21 @@ impl Declaration { warnings, errors ))], - Rule::const_decl => vec![Declaration::ConstantDeclaration(check!( - ConstantDeclaration::parse_from_pair(decl_inner, config), - return err(warnings, errors), - warnings, - errors - ))], + Rule::const_decl => { + let res_result = check!( + ConstantDeclaration::parse_from_pair(decl_inner, config), + return err(warnings, errors), + warnings, + errors + ); + let mut decls = res_result + .var_decls + .into_iter() + .map(Declaration::VariableDeclaration) + .collect::>(); + decls.push(Declaration::ConstantDeclaration(res_result.value)); + decls + } Rule::abi_decl => vec![Declaration::AbiDeclaration(check!( AbiDeclaration::parse_from_pair(decl_inner, config), return err(warnings, errors), diff --git a/sway-core/src/parse_tree/declaration/constant.rs b/sway-core/src/parse_tree/declaration/constant.rs index a72a438cf6f..8f55631cfb1 100644 --- a/sway-core/src/parse_tree/declaration/constant.rs +++ b/sway-core/src/parse_tree/declaration/constant.rs @@ -1,6 +1,6 @@ use crate::{ build_config::BuildConfig, - error::{err, ok, CompileResult, Warning}, + error::{err, ok, CompileResult, ParserLifter, Warning}, parse_tree::{ident, Expression, Visibility}, parser::Rule, style::is_screaming_snake_case, @@ -23,7 +23,7 @@ impl ConstantDeclaration { pub(crate) fn parse_from_pair( pair: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let path = config.map(|c| c.path()); let mut warnings = Vec::new(); let mut errors = Vec::new(); @@ -78,12 +78,16 @@ impl ConstantDeclaration { }, Warning::NonScreamingSnakeCaseConstName { name: name.clone() } ); + let decl = ConstantDeclaration { + name, + type_ascription, + value: value.value, + visibility, + }; ok( - ConstantDeclaration { - name, - type_ascription, - value, - visibility, + ParserLifter { + var_decls: vec![], + value: decl, }, warnings, errors, diff --git a/sway-core/src/parse_tree/declaration/enum.rs b/sway-core/src/parse_tree/declaration/enum.rs index adf396d8dc5..c4684456ce9 100644 --- a/sway-core/src/parse_tree/declaration/enum.rs +++ b/sway-core/src/parse_tree/declaration/enum.rs @@ -180,6 +180,7 @@ impl EnumVariant { errors, ) } + pub(crate) fn parse_from_pairs( decl_inner: Option>, config: Option<&BuildConfig>, diff --git a/sway-core/src/parse_tree/declaration/reassignment.rs b/sway-core/src/parse_tree/declaration/reassignment.rs index 820319bfd63..f937f8e19af 100644 --- a/sway-core/src/parse_tree/declaration/reassignment.rs +++ b/sway-core/src/parse_tree/declaration/reassignment.rs @@ -1,7 +1,7 @@ use crate::{ build_config::BuildConfig, - error::{err, ok, CompileError, CompileResult}, - parse_array_index, + error::{err, ok, CompileError, CompileResult, ParserLifter}, + error_recovery_exp, parse_array_index, parse_tree::{ident, Expression}, parser::Rule, }; @@ -23,7 +23,7 @@ impl Reassignment { pub(crate) fn parse_from_pair( pair: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let path = config.map(|c| c.path()); let span = Span { span: pair.as_span(), @@ -36,31 +36,34 @@ impl Reassignment { match variable_or_struct_reassignment.as_rule() { Rule::variable_reassignment => { let mut iter = variable_or_struct_reassignment.into_inner(); - let name = check!( + let name_result = check!( Expression::parse_from_pair_inner(iter.next().unwrap(), config), return err(warnings, errors), warnings, errors ); let body = iter.next().unwrap(); - let body = check!( + let mut body_result = check!( Expression::parse_from_pair(body.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: body.as_span(), - path - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: body.as_span(), + path + })), warnings, errors ); + let mut var_decls = name_result.var_decls; + var_decls.append(&mut body_result.var_decls); + let reassign = Reassignment { + lhs: Box::new(name_result.value), + rhs: body_result.value, + span, + }; ok( - Reassignment { - lhs: Box::new(name), - rhs: body, - span, + ParserLifter { + var_decls, + value: reassign, }, warnings, errors, @@ -74,12 +77,9 @@ impl Reassignment { span: rhs.as_span(), path: path.clone(), }; - let body = check!( + let body_result = check!( Expression::parse_from_pair(rhs, config), - Expression::Tuple { - fields: vec![], - span: rhs_span - }, + ParserLifter::empty(error_recovery_exp(rhs_span)), warnings, errors ); @@ -94,7 +94,7 @@ impl Reassignment { // the first thing is either an exp or a var, everything subsequent must be // a field let mut name_parts = inner.into_inner(); - let mut expr = check!( + let mut expr_result = check!( parse_subfield_path_ensure_only_var( name_parts.next().expect("guaranteed by grammar"), config @@ -105,8 +105,8 @@ impl Reassignment { ); for name_part in name_parts { - expr = Expression::SubfieldExpression { - prefix: Box::new(expr.clone()), + let expr = Expression::SubfieldExpression { + prefix: Box::new(expr_result.value.clone()), span: Span { span: name_part.as_span(), path: path.clone(), @@ -117,14 +117,24 @@ impl Reassignment { warnings, errors ), - } + }; + expr_result = ParserLifter { + var_decls: expr_result.var_decls, + value: expr, + }; } + let mut var_decls = body_result.var_decls; + var_decls.append(&mut expr_result.var_decls); + let exp = Reassignment { + lhs: Box::new(expr_result.value), + rhs: body_result.value, + span, + }; ok( - Reassignment { - lhs: Box::new(expr), - rhs: body, - span, + ParserLifter { + var_decls, + value: exp, }, warnings, errors, @@ -138,7 +148,7 @@ impl Reassignment { fn parse_subfield_path_ensure_only_var( item: Pair, config: Option<&BuildConfig>, -) -> CompileResult { +) -> CompileResult> { let warnings = vec![]; let mut errors = vec![]; let path = config.map(|c| c.path()); @@ -161,14 +171,11 @@ fn parse_subfield_path_ensure_only_var( }, )); // construct unit expression for error recovery - let exp = Expression::Tuple { - fields: vec![], - span: Span { - span: item.as_span(), - path, - }, - }; - ok(exp, warnings, errors) + let exp_result = ParserLifter::empty(error_recovery_exp(Span { + span: item.as_span(), + path, + })); + ok(exp_result, warnings, errors) } } } @@ -187,7 +194,7 @@ fn parse_subfield_path_ensure_only_var( fn parse_call_item_ensure_only_var( item: Pair, config: Option<&BuildConfig>, -) -> CompileResult { +) -> CompileResult> { let path = config.map(|c| c.path()); let mut warnings = vec![]; let mut errors = vec![]; @@ -217,5 +224,12 @@ fn parse_call_item_ensure_only_var( } a => unreachable!("{:?}", a), }; - ok(exp, warnings, errors) + ok( + ParserLifter { + var_decls: vec![], + value: exp, + }, + warnings, + errors, + ) } diff --git a/sway-core/src/parse_tree/declaration/storage.rs b/sway-core/src/parse_tree/declaration/storage.rs index b875f02b265..c7592b0e7d5 100644 --- a/sway-core/src/parse_tree/declaration/storage.rs +++ b/sway-core/src/parse_tree/declaration/storage.rs @@ -33,7 +33,7 @@ impl StorageField { pub(crate) fn parse_from_pair( pair: Pair, conf: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let mut errors = vec![]; let mut warnings = vec![]; let mut iter = pair.into_inner(); @@ -53,17 +53,21 @@ impl StorageField { warnings, errors ); - let initializer = check!( + let initializer_result = check!( Expression::parse_from_pair(initializer, conf), return err(warnings, errors), warnings, errors ); + let res = StorageField { + name, + r#type, + initializer: initializer_result.value.clone(), + }; ok( - StorageField { - name, - r#type, - initializer, + ParserLifter { + var_decls: initializer_result.var_decls, + value: res, }, warnings, errors, @@ -75,7 +79,7 @@ impl StorageDeclaration { pub(crate) fn parse_from_pair( pair: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { debug_assert_eq!(pair.as_rule(), Rule::storage_decl); let path = config.map(|c| c.path()); let mut errors = vec![]; @@ -90,17 +94,27 @@ impl StorageDeclaration { storage_keyword.map(|x| x.as_rule()), Some(Rule::storage_keyword) ); - let fields_results: Vec> = iter + let fields_results: Vec>> = iter .next() .unwrap() .into_inner() .map(|x| StorageField::parse_from_pair(x, config)) .collect(); let mut fields: Vec = Vec::with_capacity(fields_results.len()); + let mut var_decls = vec![]; for res in fields_results { - let ok = check!(res, continue, warnings, errors); - fields.push(ok); + let mut ok = check!(res, continue, warnings, errors); + fields.push(ok.value); + var_decls.append(&mut ok.var_decls); } - ok(StorageDeclaration { fields, span }, warnings, errors) + let res = StorageDeclaration { fields, span }; + ok( + ParserLifter { + var_decls, + value: res, + }, + warnings, + errors, + ) } } diff --git a/sway-core/src/parse_tree/declaration/variable.rs b/sway-core/src/parse_tree/declaration/variable.rs index 22738d9f12e..569e4611304 100644 --- a/sway-core/src/parse_tree/declaration/variable.rs +++ b/sway-core/src/parse_tree/declaration/variable.rs @@ -88,19 +88,26 @@ impl VariableDeclaration { } else { TypeInfo::Unknown }; - let body = check!( + let body_result = check!( Expression::parse_from_pair(maybe_body, config), return err(warnings, errors), warnings, errors ); - VariableDeclaration::desugar_to_decls( - lhs, - type_ascription, - type_ascription_span, - body, - config, - ) + let mut var_decls = body_result.var_decls; + var_decls.append(&mut check!( + VariableDeclaration::desugar_to_decls( + lhs, + type_ascription, + type_ascription_span, + body_result.value, + config, + ), + return err(warnings, errors), + warnings, + errors + )); + ok(var_decls, warnings, errors) } fn desugar_to_decls( diff --git a/sway-core/src/parse_tree/expression/asm.rs b/sway-core/src/parse_tree/expression/asm.rs index 786535e412d..fc64f3bc7d8 100644 --- a/sway-core/src/parse_tree/expression/asm.rs +++ b/sway-core/src/parse_tree/expression/asm.rs @@ -1,4 +1,7 @@ -use crate::{build_config::BuildConfig, error::*, parse_tree::ident, parser::Rule, TypeInfo}; +use crate::{ + build_config::BuildConfig, error::*, parse_tree::ident, parser::Rule, TypeInfo, + VariableDeclaration, +}; use sway_types::{ident::Ident, span::Span}; @@ -20,7 +23,7 @@ impl AsmExpression { pub(crate) fn parse_from_pair( pair: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let path = config.map(|c| c.path()); let whole_block_span = Span { span: pair.as_span(), @@ -31,7 +34,7 @@ impl AsmExpression { let mut iter = pair.into_inner(); let _asm_keyword = iter.next(); let asm_registers = iter.next().unwrap(); - let asm_registers = check!( + let asm_register_result = check!( AsmRegisterDeclaration::parse_from_pair(asm_registers, config), return err(warnings, errors), warnings, @@ -81,14 +84,18 @@ impl AsmExpression { } else { TypeInfo::Tuple(Vec::new()) }); + let exp = AsmExpression { + registers: asm_register_result.value, + body: asm_op_buf, + returns: implicit_op_return, + return_type, + whole_block_span, + }; ok( - AsmExpression { - registers: asm_registers, - body: asm_op_buf, - returns: implicit_op_return, - return_type, - whole_block_span, + ParserLifter { + var_decls: asm_register_result.var_decls, + value: exp, }, warnings, errors, @@ -183,11 +190,15 @@ pub(crate) struct AsmRegisterDeclaration { } impl AsmRegisterDeclaration { - fn parse_from_pair(pair: Pair, config: Option<&BuildConfig>) -> CompileResult> { + fn parse_from_pair( + pair: Pair, + config: Option<&BuildConfig>, + ) -> CompileResult>> { let iter = pair.into_inner(); let mut warnings = Vec::new(); let mut errors = Vec::new(); let mut reg_buf: Vec = Vec::new(); + let mut var_decl_buf: Vec = vec![]; for pair in iter { assert_eq!(pair.as_rule(), Rule::asm_register_declaration); let mut iter = pair.into_inner(); @@ -199,7 +210,7 @@ impl AsmRegisterDeclaration { ); // if there is still anything in the iterator, then it is a variable expression to be // assigned to that register - let initializer = if let Some(pair) = iter.next() { + let initializer_result = if let Some(pair) = iter.next() { Some(check!( Expression::parse_from_pair(pair, config), return err(warnings, errors), @@ -209,13 +220,27 @@ impl AsmRegisterDeclaration { } else { None }; + let (initializer, mut var_decls) = match initializer_result { + Some(initializer_result) => { + (Some(initializer_result.value), initializer_result.var_decls) + } + None => (None, vec![]), + }; reg_buf.push(AsmRegisterDeclaration { name: reg_name, initializer, - }) + }); + var_decl_buf.append(&mut var_decls); } - ok(reg_buf, warnings, errors) + ok( + ParserLifter { + var_decls: var_decl_buf, + value: reg_buf, + }, + warnings, + errors, + ) } } diff --git a/sway-core/src/parse_tree/expression/match_branch.rs b/sway-core/src/parse_tree/expression/match_branch.rs index bf11cff9857..d6c3380179d 100644 --- a/sway-core/src/parse_tree/expression/match_branch.rs +++ b/sway-core/src/parse_tree/expression/match_branch.rs @@ -1,6 +1,6 @@ use crate::{build_config::BuildConfig, error::*, parser::Rule, CatchAll, CodeBlock}; -use sway_types::span; +use sway_types::{span, Span}; use pest::iterators::Pair; @@ -104,18 +104,6 @@ impl MatchBranch { } }; let result = match result.as_rule() { - Rule::expr => check!( - Expression::parse_from_pair(result.clone(), config), - Expression::Tuple { - fields: vec![], - span: span::Span { - span: result.as_span(), - path - } - }, - warnings, - errors - ), Rule::code_block => { let span = span::Span { span: result.as_span(), @@ -134,7 +122,17 @@ impl MatchBranch { span, } } - _ => unreachable!(), + a => { + let span = Span { + span: result.as_span(), + path, + }; + errors.push(CompileError::UnimplementedRule(a, span.clone())); + Expression::Tuple { + fields: vec![], + span, + } + } }; ok( MatchBranch { diff --git a/sway-core/src/parse_tree/expression/match_condition.rs b/sway-core/src/parse_tree/expression/match_condition.rs index 70e8b717703..5dc76ba1eac 100644 --- a/sway-core/src/parse_tree/expression/match_condition.rs +++ b/sway-core/src/parse_tree/expression/match_condition.rs @@ -3,12 +3,21 @@ use super::scrutinee::Scrutinee; use sway_types::span::Span; #[derive(Debug, Clone)] -pub(crate) enum MatchCondition { +pub struct CatchAll { + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum MatchCondition { CatchAll(CatchAll), Scrutinee(Scrutinee), } -#[derive(Debug, Clone)] -pub struct CatchAll { - pub span: Span, +impl MatchCondition { + pub(crate) fn span(&self) -> Span { + match self { + MatchCondition::CatchAll(catch_all) => catch_all.span.clone(), + MatchCondition::Scrutinee(scrutinee) => scrutinee.span(), + } + } } diff --git a/sway-core/src/parse_tree/expression/mod.rs b/sway-core/src/parse_tree/expression/mod.rs index f266ab40e82..b8cb836f366 100644 --- a/sway-core/src/parse_tree/expression/mod.rs +++ b/sway-core/src/parse_tree/expression/mod.rs @@ -67,11 +67,6 @@ pub enum Expression { contents: Vec, span: Span, }, - MatchExpression { - primary_expression: Box, - branches: Vec, - span: Span, - }, StructExpression { struct_name: CallPath, fields: Vec, @@ -87,6 +82,11 @@ pub enum Expression { r#else: Option>, span: Span, }, + MatchExp { + if_exp: Box, + cases_covered: Vec, + span: Span, + }, // separated into other struct for parsing reasons AsmExpression { span: Span, @@ -228,6 +228,13 @@ pub struct StructExpressionField { pub(crate) span: Span, } +pub(crate) fn error_recovery_exp(span: Span) -> Expression { + Expression::Tuple { + fields: vec![], + span, + } +} + impl Expression { pub(crate) fn core_ops_eq(arguments: Vec, span: Span) -> Expression { Expression::MethodApplication { @@ -281,10 +288,10 @@ impl Expression { Tuple { span, .. } => span, TupleIndex { span, .. } => span, Array { span, .. } => span, - MatchExpression { span, .. } => span, StructExpression { span, .. } => span, CodeBlock { span, .. } => span, IfExp { span, .. } => span, + MatchExp { span, .. } => span, AsmExpression { span, .. } => span, MethodApplication { span, .. } => span, SubfieldExpression { span, .. } => span, @@ -298,10 +305,11 @@ impl Expression { }) .clone() } + pub(crate) fn parse_from_pair( expr: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let path = config.map(|c| c.path()); let mut warnings = Vec::new(); let mut errors = Vec::new(); @@ -309,20 +317,17 @@ impl Expression { let mut expr_iter = expr.into_inner(); // first expr is always here let first_expr = expr_iter.next().unwrap(); - let first_expr = check!( + let first_expr_result = check!( Expression::parse_from_pair_inner(first_expr.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: first_expr.as_span(), - path: path.clone(), - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: first_expr.as_span(), + path: path.clone(), + })), warnings, errors ); - let mut expr_or_op_buf: Vec> = - vec![Either::Right(first_expr.clone())]; + let mut expr_result_or_op_buf: Vec>> = + vec![Either::Right(first_expr_result.clone())]; // sometimes exprs are followed by ops in the same expr while let Some(op) = expr_iter.next() { let op_str = op.as_str().to_string(); @@ -339,16 +344,13 @@ impl Expression { ); // an op is necessarily followed by an expression - let next_expr = match expr_iter.next() { + let next_expr_result = match expr_iter.next() { Some(o) => check!( Expression::parse_from_pair_inner(o.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: o.as_span(), - path: path.clone() - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: o.as_span(), + path: path.clone() + })), warnings, errors ), @@ -360,47 +362,43 @@ impl Expression { path: path.clone(), }, }); - Expression::Tuple { - fields: vec![], - span: op_span, - } + ParserLifter::empty(error_recovery_exp(op_span)) } }; // pushing these into a vec in this manner so we can re-associate according to order of // operations later - expr_or_op_buf.push(Either::Left(op)); - expr_or_op_buf.push(Either::Right(next_expr)); + expr_result_or_op_buf.push(Either::Left(op)); + expr_result_or_op_buf.push(Either::Right(next_expr_result)); /* * TODO * strategy: keep parsing until we have all of the op expressions * re-associate the expr tree with operator precedence */ } - if expr_or_op_buf.len() == 1 { - ok(first_expr, warnings, errors) + if expr_result_or_op_buf.len() == 1 { + ok(first_expr_result, warnings, errors) } else { - let expr = arrange_by_order_of_operations( - expr_or_op_buf, + let expr_result = arrange_by_order_of_operations( + expr_result_or_op_buf, Span { span: expr_for_debug.as_span(), path: path.clone(), }, ) - .unwrap_or_else(&mut warnings, &mut errors, || Expression::Tuple { - fields: vec![], - span: Span { + .unwrap_or_else(&mut warnings, &mut errors, || { + ParserLifter::empty(error_recovery_exp(Span { span: expr_for_debug.as_span(), path: path.clone(), - }, + })) }); - ok(expr, warnings, errors) + ok(expr_result, warnings, errors) } } pub(crate) fn parse_from_pair_inner( expr: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let path = config.map(|c| c.path()); let mut errors = Vec::new(); let mut warnings = Vec::new(); @@ -410,12 +408,11 @@ impl Expression { }; #[allow(unused_assignments)] let mut maybe_type_args = Vec::new(); - let parsed = match expr.as_rule() { + let parsed_result = match expr.as_rule() { Rule::literal_value => Literal::parse_from_pair(expr, config) - .map(|(value, span)| Expression::Literal { value, span }) - .unwrap_or_else(&mut warnings, &mut errors, || Expression::Tuple { - fields: vec![], - span, + .map(|(value, span)| ParserLifter::empty(Expression::Literal { value, span })) + .unwrap_or_else(&mut warnings, &mut errors, || { + ParserLifter::empty(error_recovery_exp(span)) }), Rule::func_app => { let span = Span { @@ -446,13 +443,10 @@ impl Expression { for argument in arguments.into_inner() { let arg = check!( Expression::parse_from_pair(argument.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: argument.as_span(), - path: path.clone() - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: argument.as_span(), + path: path.clone() + })), warnings, errors ); @@ -475,11 +469,23 @@ impl Expression { )); } - Expression::FunctionApplication { + let var_decls = arguments_buf + .iter() + .flat_map(|x| x.var_decls.clone()) + .collect::>(); + let arguments_buf = arguments_buf + .into_iter() + .map(|x| x.value) + .collect::>(); + let exp = Expression::FunctionApplication { name, arguments: arguments_buf, span, type_arguments: type_args_buf, + }; + ParserLifter { + var_decls, + value: exp, } } Rule::var_exp => { @@ -501,31 +507,26 @@ impl Expression { } // this is non-optional and part of the parse rule so it won't fail let name = name.unwrap(); - Expression::VariableExpression { name, span } + let exp = Expression::VariableExpression { name, span }; + ParserLifter::empty(exp) } Rule::array_exp => match expr.into_inner().next() { - None => Expression::Array { + None => ParserLifter::empty(Expression::Array { contents: Vec::new(), span, - }, + }), Some(array_elems) => check!( parse_array_elems(array_elems, config), - Expression::Tuple { - fields: vec![], - span, - }, + ParserLifter::empty(error_recovery_exp(span)), warnings, errors ), }, Rule::match_expression => { let mut expr_iter = expr.into_inner(); - let primary_expression = check!( + let primary_expression_result = check!( Expression::parse_from_pair(expr_iter.next().unwrap(), config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors ); @@ -546,12 +547,30 @@ impl Expression { ); branches.push(res); } - check!( - desugar_match_expression(primary_expression, branches, span), + let primary_expression = primary_expression_result.value; + let (if_exp, var_decl_name, cases_covered) = check!( + desugar_match_expression(&primary_expression, branches, config), return err(warnings, errors), warnings, errors - ) + ); + let mut var_decls = primary_expression_result.var_decls; + var_decls.push(VariableDeclaration { + name: var_decl_name, + type_ascription: TypeInfo::Unknown, + type_ascription_span: None, + is_mutable: false, + body: primary_expression, + }); + let exp = Expression::MatchExp { + if_exp: Box::new(if_exp), + cases_covered, + span, + }; + ParserLifter { + var_decls, + value: exp, + } } Rule::struct_expression => { let mut expr_iter = expr.into_inner(); @@ -564,6 +583,7 @@ impl Expression { ); let fields = expr_iter.next().unwrap().into_inner().collect::>(); let mut fields_buf = Vec::new(); + let mut var_decls = vec![]; for i in (0..fields.len()).step_by(2) { let name = check!( ident::parse_from_pair(fields[i].clone(), config), @@ -575,34 +595,37 @@ impl Expression { span: fields[i].as_span(), path: path.clone(), }; - let value = check!( + let mut value_result = check!( Expression::parse_from_pair(fields[i + 1].clone(), config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors ); - fields_buf.push(StructExpressionField { name, value, span }); + fields_buf.push(StructExpressionField { + name, + value: value_result.value, + span, + }); + var_decls.append(&mut value_result.var_decls); } - Expression::StructExpression { + let exp = Expression::StructExpression { struct_name, fields: fields_buf, span, + }; + ParserLifter { + var_decls, + value: exp, } } Rule::parenthesized_expression => { check!( Expression::parse_from_pair(expr.clone().into_inner().next().unwrap(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: expr.as_span(), - path, - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: expr.as_span(), + path, + })), warnings, errors ) @@ -621,10 +644,12 @@ impl Expression { warnings, errors ); - Expression::CodeBlock { + let exp = Expression::CodeBlock { contents: expr, span, - } + }; + // this assumes that all of the var_decls will be injected into the code block + ParserLifter::empty(exp) } Rule::if_exp => { let span = Span { @@ -635,40 +660,42 @@ impl Expression { let condition_pair = if_exp_pairs.next().unwrap(); let then_pair = if_exp_pairs.next().unwrap(); let else_pair = if_exp_pairs.next(); - let condition = Box::new(check!( + let condition_result = check!( Expression::parse_from_pair(condition_pair, config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors - )); - let then = Box::new(check!( + ); + let mut then_result = check!( Expression::parse_from_pair_inner(then_pair, config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors - )); - let r#else = else_pair.map(|else_pair| { - Box::new(check!( + ); + let r#else_result = else_pair.map(|else_pair| { + check!( Expression::parse_from_pair_inner(else_pair, config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors - )) + ) }); - Expression::IfExp { - condition, - then, - r#else, + let mut var_decls = condition_result.var_decls; + var_decls.append(&mut then_result.var_decls); + let mut r#else_result_decls = match r#else_result.clone() { + Some(r#else_result) => r#else_result.var_decls, + None => vec![], + }; + var_decls.append(&mut r#else_result_decls); + let exp = Expression::IfExp { + condition: Box::new(condition_result.value), + then: Box::new(then_result.value), + r#else: r#else_result.map(|x| Box::new(x.value)), span, + }; + ParserLifter { + var_decls, + value: exp, } } Rule::asm_expression => { @@ -676,15 +703,19 @@ impl Expression { span: expr.as_span(), path, }; - let asm = check!( + let asm_result = check!( AsmExpression::parse_from_pair(expr, config), return err(warnings, errors), warnings, errors ); - Expression::AsmExpression { - asm, + let exp = Expression::AsmExpression { + asm: asm_result.value, span: whole_block_span, + }; + ParserLifter { + var_decls: asm_result.var_decls, + value: exp, } } Rule::method_exp => { @@ -708,6 +739,7 @@ impl Expression { _ => None, }; + let mut var_decls_buf = Vec::new(); let mut fields_buf = Vec::new(); if let Some(params) = contract_call_params { let fields = params @@ -727,15 +759,16 @@ impl Expression { span: fields[i].as_span(), path: path.clone(), }; - let value = check!( + let ParserLifter { + value, + mut var_decls, + } = check!( Expression::parse_from_pair(fields[i + 1].clone(), config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors ); + var_decls_buf.append(&mut var_decls); fields_buf.push(StructExpressionField { name, value, span }); } } @@ -755,26 +788,30 @@ impl Expression { warnings, errors ); - let mut arguments_buf = VecDeque::new(); + let mut argument_buf = VecDeque::new(); for argument in function_arguments { - let arg = check!( + let ParserLifter { + value, + mut var_decls, + } = check!( Expression::parse_from_pair(argument.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: argument.as_span(), - path: path.clone() - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: argument.as_span(), + path: path.clone() + })), warnings, errors ); - arguments_buf.push_back(arg); + var_decls_buf.append(&mut var_decls); + argument_buf.push_back(value); } // the first thing is either an exp or a var, everything subsequent must be // a field let mut name_parts = name_parts.into_iter(); - let mut expr = check!( + let ParserLifter { + value, + mut var_decls, + } = check!( parse_subfield_path( name_parts.next().expect("guaranteed by grammar"), config @@ -783,6 +820,8 @@ impl Expression { warnings, errors ); + let mut expr = value; + var_decls_buf.append(&mut var_decls); for name_part in name_parts { expr = Expression::SubfieldExpression { @@ -797,15 +836,19 @@ impl Expression { warnings, errors ), - } + }; } - arguments_buf.push_front(expr); - Expression::MethodApplication { + argument_buf.push_front(expr); + let exp = Expression::MethodApplication { method_name: MethodName::FromModule { method_name }, contract_call_params: fields_buf, - arguments: arguments_buf.into_iter().collect(), + arguments: argument_buf.into_iter().collect(), span: whole_exp_span, + }; + ParserLifter { + var_decls: var_decls_buf, + value: exp, } } Rule::fully_qualified_method => { @@ -890,31 +933,40 @@ impl Expression { } }; - let mut arguments_buf = vec![]; + let mut argument_results_buf = vec![]; // evaluate the arguments passed in to the method if let Some(arguments) = arguments { for argument in arguments.into_inner() { - let arg = check!( + let arg_result = check!( Expression::parse_from_pair(argument.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: argument.as_span(), - path: path.clone() - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: argument.as_span(), + path: path.clone() + })), warnings, errors ); - arguments_buf.push(arg); + argument_results_buf.push(arg_result); } } - Expression::MethodApplication { + let var_decls = argument_results_buf + .iter() + .flat_map(|x| x.var_decls.clone()) + .collect::>(); + let arguments_buf = argument_results_buf + .into_iter() + .map(|x| x.value) + .collect::>(); + let exp = Expression::MethodApplication { method_name, contract_call_params: vec![], arguments: arguments_buf, span: whole_exp_span, + }; + ParserLifter { + var_decls, + value: exp, } } a => unreachable!("{:?}", a), @@ -949,16 +1001,16 @@ impl Expression { errors ); - let args = if let Some(inst) = maybe_instantiator { + let arg_results = if let Some(inst) = maybe_instantiator { let mut buf = vec![]; for exp in inst.into_inner() { - let exp = check!( + let exp_result = check!( Expression::parse_from_pair(exp, config), return err(warnings, errors), warnings, errors ); - buf.push(exp); + buf.push(exp_result); } buf } else { @@ -992,31 +1044,49 @@ impl Expression { )); } - Expression::DelineatedPath { + let var_decls = arg_results + .iter() + .flat_map(|x| x.var_decls.clone()) + .collect::>(); + let args = arg_results.into_iter().map(|x| x.value).collect::>(); + let exp = Expression::DelineatedPath { call_path: path, type_arguments: type_args_buf, args, span, + }; + ParserLifter { + var_decls, + value: exp, } } Rule::tuple_expr => { let fields = expr.into_inner().collect::>(); - let mut fields_buf = Vec::with_capacity(fields.len()); + let mut field_results_buf = Vec::with_capacity(fields.len()); for field in fields { - let value = check!( + let value_result = check!( Expression::parse_from_pair(field.clone(), config), - Expression::Tuple { - fields: vec![], - span: span.clone() - }, + ParserLifter::empty(error_recovery_exp(span.clone())), warnings, errors ); - fields_buf.push(value); + field_results_buf.push(value_result); } - Expression::Tuple { + let var_decls = field_results_buf + .iter() + .flat_map(|x| x.var_decls.clone()) + .collect::>(); + let fields_buf = field_results_buf + .into_iter() + .map(|x| x.value) + .collect::>(); + let exp = Expression::Tuple { fields: fields_buf, span, + }; + ParserLifter { + var_decls, + value: exp, } } Rule::tuple_index => { @@ -1027,7 +1097,7 @@ impl Expression { let mut inner = expr.into_inner(); let call_item = inner.next().expect("guarenteed by grammar"); assert_eq!(call_item.as_rule(), Rule::call_item); - let prefix = check!( + let prefix_result = check!( parse_call_item(call_item, config), return err(warnings, errors), warnings, @@ -1054,11 +1124,15 @@ impl Expression { return err(warnings, errors); } }; - Expression::TupleIndex { - prefix: Box::new(prefix), + let exp = Expression::TupleIndex { + prefix: Box::new(prefix_result.value), index, index_span: the_integer_span, span, + }; + ParserLifter { + var_decls: prefix_result.var_decls, + value: exp, } } Rule::struct_field_access => { @@ -1066,7 +1140,7 @@ impl Expression { assert_eq!(inner.as_rule(), Rule::subfield_path); let mut name_parts = inner.into_inner(); - let mut expr = check!( + let mut expr_result = check!( parse_subfield_path(name_parts.next().expect("guaranteed by grammar"), config), return err(warnings, errors), warnings, @@ -1074,8 +1148,8 @@ impl Expression { ); for name_part in name_parts { - expr = Expression::SubfieldExpression { - prefix: Box::new(expr.clone()), + let new_expr = Expression::SubfieldExpression { + prefix: Box::new(expr_result.value.clone()), span: Span { span: name_part.as_span(), path: path.clone(), @@ -1086,10 +1160,14 @@ impl Expression { warnings, errors ), - } + }; + expr_result = ParserLifter { + var_decls: expr_result.var_decls, + value: new_expr, + }; } - expr + expr_result } Rule::abi_cast => { let span = Span { @@ -1106,16 +1184,20 @@ impl Expression { errors ); let address = iter.next().expect("guaranteed by grammar"); - let address = check!( + let address_result = check!( Expression::parse_from_pair(address, config), return err(warnings, errors), warnings, errors ); - Expression::AbiCast { + let exp = Expression::AbiCast { span, - address: Box::new(address), + address: Box::new(address_result.value.clone()), abi_name, + }; + ParserLifter { + var_decls: address_result.var_decls, + value: exp, } } Rule::unary_op_expr => { @@ -1159,28 +1241,25 @@ impl Expression { }, )); // construct unit expression for error recovery - Expression::Tuple { - fields: vec![], - span: Span { - span: expr.as_span(), - path, - }, - } + ParserLifter::empty(error_recovery_exp(Span { + span: expr.as_span(), + path, + })) } }; - ok(parsed, warnings, errors) + ok(parsed_result, warnings, errors) } } fn convert_unary_to_fn_calls( item: Pair, config: Option<&BuildConfig>, -) -> CompileResult { +) -> CompileResult> { let iter = item.into_inner(); let mut unary_stack = vec![]; let mut warnings = vec![]; let mut errors = vec![]; - let mut expr = None; + let mut expr_result = None; for item in iter { match item.as_rule() { Rule::unary_op => unary_stack.push(( @@ -1196,7 +1275,7 @@ fn convert_unary_to_fn_calls( ), )), _ => { - expr = Some(check!( + expr_result = Some(check!( Expression::parse_from_pair_inner(item, config), return err(warnings, errors), warnings, @@ -1206,68 +1285,85 @@ fn convert_unary_to_fn_calls( } } - let mut expr = expr.expect("guaranteed by grammar"); + let mut expr_result = expr_result.expect("guaranteed by grammar"); assert!(!unary_stack.is_empty(), "guaranteed by grammar"); while let Some((op_span, unary_op)) = unary_stack.pop() { - expr = unary_op.to_fn_application( - expr.clone(), - join_spans(op_span.clone(), expr.span()), + let exp = unary_op.to_fn_application( + expr_result.value.clone(), + join_spans(op_span.clone(), expr_result.value.span()), op_span, ); + expr_result = ParserLifter { + var_decls: expr_result.var_decls, + value: exp, + }; } - ok(expr, warnings, errors) + ok(expr_result, warnings, errors) } pub(crate) fn parse_array_index( item: Pair, config: Option<&BuildConfig>, -) -> CompileResult { +) -> CompileResult> { let mut warnings = vec![]; let mut errors = vec![]; let path = config.map(|c| c.path()); let span = item.as_span(); let mut inner_iter = item.into_inner(); - let prefix = check!( + let prefix_result = check!( parse_call_item(inner_iter.next().unwrap(), config), return err(warnings, errors), warnings, errors ); - let mut index_buf = vec![]; + let mut index_result_buf = vec![]; for index in inner_iter { - index_buf.push(check!( + index_result_buf.push(check!( Expression::parse_from_pair(index, config), return err(warnings, errors), warnings, errors )); } - let first_index = index_buf.first().expect("guarenteed by grammer"); + let mut first_result_index = index_result_buf + .first() + .expect("guarenteed by grammer") + .to_owned(); + let mut var_decls = prefix_result.var_decls; + var_decls.append(&mut first_result_index.var_decls); let mut exp = Expression::ArrayIndex { - prefix: Box::new(prefix), - index: Box::new(first_index.to_owned()), + prefix: Box::new(prefix_result.value), + index: Box::new(first_result_index.value.to_owned()), span: Span { span: span.clone(), path: path.clone(), }, }; - for index in index_buf.into_iter().skip(1) { + for mut index_result in index_result_buf.into_iter().skip(1) { + var_decls.append(&mut index_result.var_decls); exp = Expression::ArrayIndex { prefix: Box::new(exp), - index: Box::new(index), + index: Box::new(index_result.value), span: Span { span: span.clone(), path: path.clone(), }, }; } - ok(exp, warnings, errors) + ok( + ParserLifter { + var_decls, + value: exp, + }, + warnings, + errors, + ) } pub(crate) fn parse_size_of_expr( item: Pair, config: Option<&BuildConfig>, -) -> CompileResult { +) -> CompileResult> { let mut warnings = vec![]; let mut errors = vec![]; let span = Span { @@ -1281,15 +1377,19 @@ pub(crate) fn parse_size_of_expr( let mut inner_iter = size_of.into_inner(); let _keyword = inner_iter.next(); let elem = inner_iter.next().expect("guarenteed by grammar"); - let expr = check!( + let expr_result = check!( Expression::parse_from_pair(elem, config), return err(warnings, errors), warnings, errors ); - Expression::SizeOfVal { - exp: Box::new(expr), + let exp = Expression::SizeOfVal { + exp: Box::new(expr_result.value), span, + }; + ParserLifter { + var_decls: expr_result.var_decls, + value: exp, } } Rule::size_of_type_expr => { @@ -1306,11 +1406,12 @@ pub(crate) fn parse_size_of_expr( warnings, errors ); - Expression::SizeOfType { + let exp = Expression::SizeOfType { type_name, type_span, span, - } + }; + ParserLifter::empty(exp) } _ => unreachable!(), }; @@ -1320,7 +1421,7 @@ pub(crate) fn parse_size_of_expr( fn parse_subfield_path( item: Pair, config: Option<&BuildConfig>, -) -> CompileResult { +) -> CompileResult> { let warnings = vec![]; let mut errors = vec![]; let path = config.map(|c| c.path()); @@ -1343,14 +1444,11 @@ fn parse_subfield_path( }, )); // construct unit expression for error recovery - let exp = Expression::Tuple { - fields: vec![], - span: Span { - span: item.as_span(), - path, - }, - }; - ok(exp, warnings, errors) + let exp_result = ParserLifter::empty(error_recovery_exp(Span { + span: item.as_span(), + path, + })); + ok(exp_result, warnings, errors) } } } @@ -1358,24 +1456,30 @@ fn parse_subfield_path( // A call item is parsed as either an `ident` or a parenthesized `expr`. This method's job is to // figure out which variant of `call_item` this is and turn it into either a variable expression // or parse it as an expression otherwise. -fn parse_call_item(item: Pair, config: Option<&BuildConfig>) -> CompileResult { +fn parse_call_item( + item: Pair, + config: Option<&BuildConfig>, +) -> CompileResult> { let mut warnings = vec![]; let mut errors = vec![]; assert_eq!(item.as_rule(), Rule::call_item); let item = item.into_inner().next().expect("guaranteed by grammar"); - let exp = match item.as_rule() { - Rule::ident => Expression::VariableExpression { - name: check!( - ident::parse_from_pair(item.clone(), config), - return err(warnings, errors), - warnings, - errors - ), - span: Span { - span: item.as_span(), - path: config.map(|c| c.path()), - }, - }, + let exp_result = match item.as_rule() { + Rule::ident => { + let exp = Expression::VariableExpression { + name: check!( + ident::parse_from_pair(item.clone(), config), + return err(warnings, errors), + warnings, + errors + ), + span: Span { + span: item.as_span(), + path: config.map(|c| c.path()), + }, + }; + ParserLifter::empty(exp) + } Rule::expr => check!( Expression::parse_from_pair(item, config), return err(warnings, errors), @@ -1384,10 +1488,13 @@ fn parse_call_item(item: Pair, config: Option<&BuildConfig>) -> CompileRes ), a => unreachable!("{:?}", a), }; - ok(exp, warnings, errors) + ok(exp_result, warnings, errors) } -fn parse_array_elems(elems: Pair, config: Option<&BuildConfig>) -> CompileResult { +fn parse_array_elems( + elems: Pair, + config: Option<&BuildConfig>, +) -> CompileResult> { let mut warnings = Vec::new(); let mut errors = Vec::new(); @@ -1404,10 +1511,9 @@ fn parse_array_elems(elems: Pair, config: Option<&BuildConfig>) -> Compile // The form [initialiser; count]. let span = first_elem.as_span(); let init = Literal::parse_from_pair(first_elem, config) - .map(|(value, span)| Expression::Literal { value, span }) - .unwrap_or_else(&mut warnings, &mut errors, || Expression::Tuple { - fields: vec![], - span: Span { span, path }, + .map(|(value, span)| ParserLifter::empty(Expression::Literal { value, span })) + .unwrap_or_else(&mut warnings, &mut errors, || { + ParserLifter::empty(error_recovery_exp(Span { span, path })) }); // This is a constant integer expression we need to parse now into a count. Currently @@ -1427,29 +1533,23 @@ fn parse_array_elems(elems: Pair, config: Option<&BuildConfig>) -> Compile _otherwise => { // The simple form [elem0, elem1, ..., elemN]. let span = first_elem.as_span(); - let first_elem_expr = check!( + let first_elem_expr_result = check!( Expression::parse_from_pair(first_elem, config), - Expression::Tuple { - fields: vec![], - span: Span { - span, - path: path.clone() - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span, + path: path.clone() + })), warnings, errors ); - elem_iter.fold(vec![first_elem_expr], |mut elems, pair| { + elem_iter.fold(vec![first_elem_expr_result], |mut elems, pair| { let span = pair.as_span(); elems.push(check!( Expression::parse_from_pair(pair, config), - Expression::Tuple { - fields: vec![], - span: Span { - span, - path: path.clone() - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span, + path: path.clone() + })), warnings, errors )); @@ -1458,7 +1558,21 @@ fn parse_array_elems(elems: Pair, config: Option<&BuildConfig>) -> Compile } }; - ok(Expression::Array { contents, span }, warnings, errors) + let var_decls = contents + .iter() + .flat_map(|x| x.var_decls.clone()) + .collect::>(); + let exps = contents.into_iter().map(|x| x.value).collect::>(); + let exp = Expression::Array { + contents: exps, + span, + }; + let parse_result = ParserLifter { + var_decls, + value: exp, + }; + + ok(parse_result, warnings, errors) } fn parse_op(op: Pair, config: Option<&BuildConfig>) -> CompileResult { @@ -1590,16 +1704,16 @@ impl OpVariant { } fn arrange_by_order_of_operations( - expressions: Vec>, + expression_results: Vec>>, debug_span: Span, -) -> CompileResult { +) -> CompileResult> { let mut errors = Vec::new(); let warnings = Vec::new(); - let mut expression_stack = Vec::new(); + let mut expression_result_stack: Vec> = Vec::new(); let mut op_stack = Vec::new(); - for expr_or_op in expressions { - match expr_or_op { + for expr_result_or_op in expression_results { + match expr_result_or_op { Either::Left(op) => { if op.op_variant.precedence() < op_stack @@ -1607,8 +1721,8 @@ fn arrange_by_order_of_operations( .map(|x: &Op| x.op_variant.precedence()) .unwrap_or(0) { - let rhs = expression_stack.pop(); - let lhs = expression_stack.pop(); + let rhs = expression_result_stack.pop(); + let lhs = expression_result_stack.pop(); let new_op = op_stack.pop().unwrap(); if lhs.is_none() { errors.push(CompileError::Internal( @@ -1625,30 +1739,40 @@ fn arrange_by_order_of_operations( return err(warnings, errors); } let lhs = lhs.unwrap(); - let rhs = rhs.unwrap(); + let mut rhs = rhs.unwrap(); // We special case `&&` and `||` here because they are binary operators and are // bound by the precedence rules, but they are not overloaded by std::ops since // they must be evaluated lazily. - expression_stack.push(match new_op.op_variant { + let mut new_var_decls = lhs.var_decls; + new_var_decls.append(&mut rhs.var_decls); + let new_exp = match new_op.op_variant { OpVariant::And | OpVariant::Or => Expression::LazyOperator { op: LazyOp::from(new_op.op_variant), - lhs: Box::new(lhs), - rhs: Box::new(rhs), + lhs: Box::new(lhs.value), + rhs: Box::new(rhs.value), span: debug_span.clone(), }, - _ => Expression::core_ops(new_op, vec![lhs, rhs], debug_span.clone()), - }) + _ => Expression::core_ops( + new_op, + vec![lhs.value, rhs.value], + debug_span.clone(), + ), + }; + expression_result_stack.push(ParserLifter { + var_decls: new_var_decls, + value: new_exp, + }); } op_stack.push(op) } - Either::Right(expr) => expression_stack.push(expr), + Either::Right(expr_result) => expression_result_stack.push(expr_result), } } while let Some(op) = op_stack.pop() { - let rhs = expression_stack.pop(); - let lhs = expression_stack.pop(); + let rhs = expression_result_stack.pop(); + let lhs = expression_result_stack.pop(); if lhs.is_none() { errors.push(CompileError::Internal( @@ -1666,22 +1790,31 @@ fn arrange_by_order_of_operations( } let lhs = lhs.unwrap(); - let rhs = rhs.unwrap(); + let mut rhs = rhs.unwrap(); // See above about special casing `&&` and `||`. - let span = join_spans(join_spans(lhs.span(), op.span.clone()), rhs.span()); - expression_stack.push(match op.op_variant { + let span = join_spans( + join_spans(lhs.value.span(), op.span.clone()), + rhs.value.span(), + ); + let mut new_var_decls = lhs.var_decls; + new_var_decls.append(&mut rhs.var_decls); + let new_exp = match op.op_variant { OpVariant::And | OpVariant::Or => Expression::LazyOperator { op: LazyOp::from(op.op_variant), - lhs: Box::new(lhs), - rhs: Box::new(rhs), + lhs: Box::new(lhs.value), + rhs: Box::new(rhs.value), span, }, - _ => Expression::core_ops(op, vec![lhs.clone(), rhs.clone()], span), + _ => Expression::core_ops(op, vec![lhs.value.clone(), rhs.value.clone()], span), + }; + expression_result_stack.push(ParserLifter { + var_decls: new_var_decls, + value: new_exp, }); } - if expression_stack.len() != 1 { + if expression_result_stack.len() != 1 { errors.push(CompileError::Internal( "Invalid expression stack length", debug_span, @@ -1689,7 +1822,7 @@ fn arrange_by_order_of_operations( return err(warnings, errors); } - ok(expression_stack[0].clone(), warnings, errors) + ok(expression_result_stack[0].clone(), warnings, errors) } struct MatchedBranch { @@ -1724,10 +1857,11 @@ struct MatchedBranch { /// The resulting if statement would look roughly like this: /// /// ```ignore -/// if y==5 { +/// let NEW_NAME = p; +/// if NEW_NAME.y==5 { /// let x = 42; /// x -/// } else if y==42 { +/// } else if NEW_NAME.y==42 { /// let x = 42; /// x /// } else { @@ -1737,20 +1871,29 @@ struct MatchedBranch { /// /// The steps of the algorithm can roughly be broken down into: /// +/// 0. Create a VariableDeclaration that assigns the primary expression to a variable. /// 1. Assemble the "matched branches." /// 2. Assemble the possibly nested giant if statement using the matched branches. /// 2a. Assemble the conditional that goes in the if primary expression. /// 2b. Assemble the statements that go inside of the body of the if expression /// 2c. Assemble the giant if statement. /// 3. Return! -pub fn desugar_match_expression( - primary_expression: Expression, +pub(crate) fn desugar_match_expression( + primary_expression: &Expression, branches: Vec, - _span: Span, -) -> CompileResult { + config: Option<&BuildConfig>, +) -> CompileResult<(Expression, Ident, Vec)> { let mut errors = vec![]; let mut warnings = vec![]; + // 0. Create a VariableDeclaration that assigns the primary expression to a variable. + let var_decl_span = primary_expression.span(); + let var_decl_name = ident::random_name(var_decl_span.clone(), config); + let var_decl_exp = Expression::VariableExpression { + name: var_decl_name.clone(), + span: var_decl_span, + }; + // 1. Assemble the "matched branches." let mut matched_branches = vec![]; for MatchBranch { @@ -1762,7 +1905,7 @@ pub fn desugar_match_expression( let matches = match condition { MatchCondition::CatchAll(_) => Some((vec![], vec![])), MatchCondition::Scrutinee(scrutinee) => check!( - matcher(&primary_expression, scrutinee), + matcher(&var_decl_exp, scrutinee), return err(warnings, errors), warnings, errors @@ -1778,15 +1921,12 @@ pub fn desugar_match_expression( }); } None => { - let errors = vec![CompileError::PatternMatchingAlgorithmFailure( - "found None", - branch_span.clone(), - )]; + let errors = vec![CompileError::Internal("found None", branch_span.clone())]; let exp = Expression::Tuple { fields: vec![], span: branch_span.clone(), }; - return ok(exp, vec![], errors); + return ok((exp, var_decl_name, vec![]), vec![], errors); } } } @@ -1951,19 +2091,30 @@ pub fn desugar_match_expression( fields: vec![], span: if_statement.span(), }; - return ok(exp, warnings, errors); + return ok((exp, var_decl_name, vec![]), warnings, errors); } } } // 3. Return! + let cases_covered = branches + .into_iter() + .map(|x| x.condition) + .collect::>(); match if_statement { None => err(vec![], vec![]), - Some(if_statement) => ok(if_statement, warnings, errors), + Some(if_statement) => ok( + (if_statement, var_decl_name, cases_covered), + warnings, + errors, + ), } } -fn parse_if_let(expr: Pair, config: Option<&BuildConfig>) -> CompileResult { +fn parse_if_let( + expr: Pair, + config: Option<&BuildConfig>, +) -> CompileResult> { let mut warnings = vec![]; let mut errors = vec![]; let path = config.map(|c| c.path()); @@ -1988,7 +2139,10 @@ fn parse_if_let(expr: Pair, config: Option<&BuildConfig>) -> CompileResult errors ); - let expr = check!( + let ParserLifter { + mut var_decls, + value: expr, + } = check!( Expression::parse_from_pair(expr_pair, config), return err(warnings, errors), warnings, @@ -2033,12 +2187,16 @@ fn parse_if_let(expr: Pair, config: Option<&BuildConfig>) -> CompileResult Some(Box::new(exp)) } Rule::if_let_exp => { - let r#else = check!( + let ParserLifter { + var_decls: mut var_decls2, + value: r#else, + } = check!( parse_if_let(else_branch.clone(), config), return err(warnings, errors), warnings, errors ); + var_decls.append(&mut var_decls2); Some(Box::new(r#else)) } _ => unreachable!("guaranteed by grammar"), @@ -2046,15 +2204,16 @@ fn parse_if_let(expr: Pair, config: Option<&BuildConfig>) -> CompileResult } else { None }; - ok( - Expression::IfLet { - scrutinee, - expr: Box::new(expr), - then, - r#else: maybe_else_branch, - span, - }, - warnings, - errors, - ) + let exp = Expression::IfLet { + scrutinee, + expr: Box::new(expr), + then, + r#else: maybe_else_branch, + span, + }; + let result = ParserLifter { + var_decls, + value: exp, + }; + ok(result, warnings, errors) } diff --git a/sway-core/src/parse_tree/return_statement.rs b/sway-core/src/parse_tree/return_statement.rs index 383149b8868..0c6243a568c 100644 --- a/sway-core/src/parse_tree/return_statement.rs +++ b/sway-core/src/parse_tree/return_statement.rs @@ -1,4 +1,10 @@ -use crate::{build_config::BuildConfig, error::ok, parser::Rule, CompileResult, Expression}; +use crate::{ + build_config::BuildConfig, + error::{ok, ParserLifter}, + error_recovery_exp, + parser::Rule, + CompileResult, Expression, +}; use sway_types::span; @@ -13,7 +19,7 @@ impl ReturnStatement { pub(crate) fn parse_from_pair( pair: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let span = span::Span { span: pair.as_span(), path: config.map(|c| c.path()), @@ -24,23 +30,29 @@ impl ReturnStatement { let _ret_keyword = inner.next(); let expr = inner.next(); let res = match expr { - None => ReturnStatement { - expr: Expression::Tuple { - fields: vec![], - span, - }, - }, - Some(expr_pair) => { - let expr = check!( - Expression::parse_from_pair(expr_pair, config), - Expression::Tuple { + None => { + let stmt = ReturnStatement { + expr: Expression::Tuple { fields: vec![], - span + span, }, + }; + ParserLifter::empty(stmt) + } + Some(expr_pair) => { + let expr_result = check!( + Expression::parse_from_pair(expr_pair, config), + ParserLifter::empty(error_recovery_exp(span)), warnings, errors ); - ReturnStatement { expr } + let stmt = ReturnStatement { + expr: expr_result.value, + }; + ParserLifter { + var_decls: expr_result.var_decls, + value: stmt, + } } }; ok(res, warnings, errors) diff --git a/sway-core/src/parse_tree/while_loop.rs b/sway-core/src/parse_tree/while_loop.rs index 1e5c02dd752..9142df572b2 100644 --- a/sway-core/src/parse_tree/while_loop.rs +++ b/sway-core/src/parse_tree/while_loop.rs @@ -1,6 +1,7 @@ use crate::{ build_config::BuildConfig, - error::{ok, CompileResult}, + error::{ok, CompileResult, ParserLifter}, + error_recovery_exp, parser::Rule, CodeBlock, Expression, }; @@ -20,7 +21,7 @@ impl WhileLoop { pub(crate) fn parse_from_pair( pair: Pair, config: Option<&BuildConfig>, - ) -> CompileResult { + ) -> CompileResult> { let path = config.map(|c| c.path()); let mut warnings = Vec::new(); let mut errors = Vec::new(); @@ -33,15 +34,12 @@ impl WhileLoop { path: path.clone(), }; - let condition = check!( + let condition_result = check!( Expression::parse_from_pair(condition.clone(), config), - Expression::Tuple { - fields: vec![], - span: Span { - span: condition.as_span(), - path, - } - }, + ParserLifter::empty(error_recovery_exp(Span { + span: condition.as_span(), + path, + })), warnings, errors ); @@ -55,7 +53,18 @@ impl WhileLoop { warnings, errors ); + let while_loop = WhileLoop { + condition: condition_result.value, + body, + }; - ok(WhileLoop { condition, body }, warnings, errors) + ok( + ParserLifter { + var_decls: condition_result.var_decls, + value: while_loop, + }, + warnings, + errors, + ) } } diff --git a/sway-core/src/semantic_analysis/ast_node/expression/mod.rs b/sway-core/src/semantic_analysis/ast_node/expression/mod.rs index c88ce1befd7..cb716f673a5 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/mod.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/mod.rs @@ -3,8 +3,10 @@ mod func_app_instantiation; mod struct_expr_field; mod typed_expression; mod typed_expression_variant; +mod usefulness; pub(crate) use enum_instantiation::instantiate_enum; pub(crate) use func_app_instantiation::instantiate_function_application; pub(crate) use struct_expr_field::TypedStructExpressionField; pub(crate) use typed_expression::{error_recovery_expr, TypedExpression}; pub(crate) use typed_expression_variant::*; +pub(crate) use usefulness::check_match_expression_usefulness; diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 11ce5bfb03e..3e8e1e12c8a 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -256,6 +256,25 @@ impl TypedExpression { }, span, ), + Expression::MatchExp { + if_exp, + span, + cases_covered, + } => Self::type_check_match_expression( + TypeCheckArguments { + checkee: (*if_exp, cases_covered), + return_type_annotation: type_annotation, + namespace, + crate_namespace, + self_type, + build_config, + dead_code_graph, + mode: Mode::NonAbi, + help_text: Default::default(), + opts, + }, + span, + ), Expression::AsmExpression { asm, span, .. } => Self::type_check_asm_expression( asm, span, @@ -470,6 +489,7 @@ impl TypedExpression { }, span, ), + /* a => { let errors = vec![CompileError::Unimplemented( "Unimplemented type checking for expression", @@ -479,6 +499,7 @@ impl TypedExpression { let exp = error_recovery_expr(a.span()); ok(exp, vec![], errors) } + */ }; let mut typed_expression = match res.value { Some(r) => r, @@ -575,7 +596,7 @@ impl TypedExpression { ok(exp, vec![], vec![]) } - fn type_check_variable_expression( + pub(crate) fn type_check_variable_expression( name: Ident, span: Span, namespace: crate::semantic_analysis::NamespaceRef, @@ -1171,6 +1192,66 @@ impl TypedExpression { ok(exp, warnings, errors) } + #[allow(clippy::type_complexity)] + fn type_check_match_expression( + arguments: TypeCheckArguments<'_, (Expression, Vec)>, + span: Span, + ) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let TypeCheckArguments { + checkee: (if_exp, cases_covered), + namespace, + crate_namespace, + return_type_annotation: type_annotation, + self_type, + build_config, + dead_code_graph, + opts, + .. + } = arguments; + let args = TypeCheckArguments { + checkee: if_exp.clone(), + namespace, + crate_namespace, + return_type_annotation: type_annotation, + help_text: Default::default(), + self_type, + build_config, + dead_code_graph, + mode: Mode::NonAbi, + opts, + }; + let typed_if_exp = check!( + TypedExpression::type_check(args), + error_recovery_expr(if_exp.span()), + warnings, + errors + ); + let (witness_report, arms_reachability) = check!( + check_match_expression_usefulness(cases_covered, span.clone()), + return err(warnings, errors), + warnings, + errors + ); + for (arm, reachable) in arms_reachability.into_iter() { + if !reachable { + warnings.push(CompileWarning { + span: arm.span(), + warning_content: Warning::MatchExpressionUnreachableArm, + }); + } + } + if witness_report.has_witnesses() { + errors.push(CompileError::MatchExpressionNonExhaustive { + missing_patterns: format!("{}", witness_report), + span, + }); + return err(warnings, errors); + } + ok(typed_if_exp, warnings, errors) + } + #[allow(clippy::too_many_arguments)] fn type_check_asm_expression( asm: AsmExpression, diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 1dfc1bccbc0..7288a86a16b 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -5,6 +5,7 @@ use crate::control_flow_analysis::ControlFlowGraph; use crate::parse_tree::{MethodName, StructExpressionField}; use crate::parser::{Rule, SwayParser}; use crate::semantic_analysis::TCOpts; +use pest::iterators::Pairs; use pest::Parser; use std::collections::{HashMap, VecDeque}; @@ -221,6 +222,44 @@ pub(crate) fn type_check_method_application( .map(|(param, arg)| (param.name.clone(), arg)) .collect::>(); + let selector = if method.is_contract_call { + let contract_address = match contract_caller + .map(|x| crate::type_engine::look_up_type_id(x.return_type)) + { + Some(TypeInfo::ContractCaller { address, .. }) => address, + _ => { + errors.push(CompileError::Internal( + "Attempted to find contract address of non-contract-call.", + span.clone(), + )); + String::new() + } + }; + // TODO(static span): this can be a normal address expression, + // so we don't need to re-parse and re-compile + let contract_address = check!( + re_parse_expression( + contract_address.into(), + build_config, + namespace, + crate_namespace, + self_type, + dead_code_graph, + opts, + ), + return err(warnings, errors), + warnings, + errors + ); + let func_selector = check!(method.to_fn_selector_value(), [0; 4], warnings, errors); + Some(ContractCallMetadata { + func_selector, + contract_address: Box::new(contract_address), + }) + } else { + None + }; + TypedExpression { expression: TypedExpressionVariant::FunctionApplication { name: CallPath { @@ -231,44 +270,7 @@ pub(crate) fn type_check_method_application( contract_call_params: contract_call_params_map, arguments: args_and_names, function_body: method.body.clone(), - selector: if method.is_contract_call { - let contract_address = match contract_caller - .map(|x| crate::type_engine::look_up_type_id(x.return_type)) - { - Some(TypeInfo::ContractCaller { address, .. }) => address, - _ => { - errors.push(CompileError::Internal( - "Attempted to find contract address of non-contract-call.", - span.clone(), - )); - String::new() - } - }; - // TODO(static span): this can be a normal address expression, - // so we don't need to re-parse and re-compile - let contract_address = check!( - re_parse_expression( - contract_address.into(), - build_config, - namespace, - crate_namespace, - self_type, - dead_code_graph, - opts, - ), - return err(warnings, errors), - warnings, - errors - ); - let func_selector = - check!(method.to_fn_selector_value(), [0; 4], warnings, errors); - Some(ContractCallMetadata { - func_selector, - contract_address: Box::new(contract_address), - }) - } else { - None - }, + selector, }, return_type: method.return_type, is_constant: IsConstant::No, @@ -378,7 +380,7 @@ fn re_parse_expression( path: None, }; - let mut contract_pairs = match SwayParser::parse(Rule::expr, contract_string) { + let mut contract_pairs: Pairs = match SwayParser::parse(Rule::expr, contract_string) { Ok(o) => o, Err(_e) => { errors.push(CompileError::Internal( @@ -399,15 +401,17 @@ fn re_parse_expression( } }; - let contract_address = check!( + // purposefully ignore var_decls as those have already been lifted during parsing + let ParserLifter { value, .. } = check!( Expression::parse_from_pair(contract_pair, Some(build_config)), return err(warnings, errors), warnings, errors ); + let contract_address = check!( TypedExpression::type_check(TypeCheckArguments { - checkee: contract_address, + checkee: value, namespace, crate_namespace, return_type_annotation: insert_type(TypeInfo::Unknown), diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/constructor_factory.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/constructor_factory.rs new file mode 100644 index 00000000000..e03e491c966 --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/constructor_factory.rs @@ -0,0 +1,286 @@ +use sway_types::Span; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, +}; + +use super::{ + patstack::PatStack, + pattern::{Pattern, StructPattern}, + range::Range, +}; + +pub(crate) struct ConstructorFactory {} + +impl ConstructorFactory { + pub(crate) fn new() -> Self { + ConstructorFactory {} + } + + /// Given Σ, computes a `Pattern` not present in Σ from the type of the + /// elements of Σ. If more than one `Pattern` is found, these patterns are + /// wrapped in an or-pattern. + /// + /// For example, given this Σ: + /// + /// ```ignore + /// [ + /// Pattern::U64(Range { first: std::u64::MIN, last: 3 }), + /// Pattern::U64(Range { first: 16, last: std::u64::MAX }) + /// ] + /// ``` + /// + /// this would result in this `Pattern`: + /// + /// ```ignore + /// Pattern::U64(Range { first: 4, last: 15 }) + /// ``` + /// + /// Given this Σ (which is more likely to occur than the above example): + /// + /// ```ignore + /// [ + /// Pattern::U64(Range { first: 2, last: 3 }), + /// Pattern::U64(Range { first: 16, last: 17 }) + /// ] + /// ``` + /// + /// this would result in this `Pattern`: + /// + /// ```ignore + /// Pattern::Or([ + /// Pattern::U64(Range { first: std::u64::MIN, last: 1 }), + /// Pattern::U64(Range { first: 4, last: 15 }), + /// Pattern::U64(Range { first: 18, last: std::u64::MAX }) + /// ]) + /// ``` + pub(crate) fn create_pattern_not_present( + &self, + sigma: PatStack, + span: &Span, + ) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let (first, rest) = check!( + sigma.flatten().filter_out_wildcards().split_first(span), + return err(warnings, errors), + warnings, + errors + ); + let pat = match first { + Pattern::U8(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U8(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + let unincluded: PatStack = check!( + Range::find_exclusionary_ranges(ranges, Range::u8(), span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::U8) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(unincluded, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::U16(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U16(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + let unincluded: PatStack = check!( + Range::find_exclusionary_ranges(ranges, Range::u16(), span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::U16) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(unincluded, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::U32(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U32(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + let unincluded: PatStack = check!( + Range::find_exclusionary_ranges(ranges, Range::u32(), span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::U32) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(unincluded, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::U64(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U64(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + let unincluded: PatStack = check!( + Range::find_exclusionary_ranges(ranges, Range::u64(), span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::U64) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(unincluded, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::Numeric(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::Numeric(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + let unincluded: PatStack = check!( + Range::find_exclusionary_ranges(ranges, Range::u64(), span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::Numeric) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(unincluded, span), + return err(warnings, errors), + warnings, + errors + ) + } + // we will not present every string case + Pattern::String(_) => Pattern::Wildcard, + Pattern::Wildcard => unreachable!(), + // we will not present every b256 case + Pattern::B256(_) => Pattern::Wildcard, + Pattern::Boolean(b) => { + let mut true_found = false; + let mut false_found = false; + if b { + true_found = true; + } else { + false_found = true; + } + if rest.contains(&Pattern::Boolean(true)) { + true_found = true; + } else if rest.contains(&Pattern::Boolean(false)) { + false_found = true; + } + if true_found && false_found { + errors.push(CompileError::Internal( + "unable to create a new pattern", + span.clone(), + )); + return err(warnings, errors); + } else if true_found { + Pattern::Boolean(false) + } else { + Pattern::Boolean(true) + } + } + Pattern::Byte(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::Byte(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + let unincluded: PatStack = check!( + Range::find_exclusionary_ranges(ranges, Range::u8(), span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::Byte) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(unincluded, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::Struct(struct_pattern) => { + let fields = struct_pattern + .fields() + .iter() + .map(|(name, _)| (name.clone(), Pattern::Wildcard)) + .collect::>(); + Pattern::Struct(StructPattern::new( + struct_pattern.struct_name().clone(), + fields, + )) + } + Pattern::Tuple(elems) => Pattern::Tuple(PatStack::fill_wildcards(elems.len())), + Pattern::Or(_) => unreachable!(), + }; + ok(pat, warnings, errors) + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/matrix.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/matrix.rs new file mode 100644 index 00000000000..059717a0282 --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/matrix.rs @@ -0,0 +1,124 @@ +use sway_types::Span; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, +}; + +use super::patstack::PatStack; + +/// A `Matrix` is a `Vec` that is implemented with special methods +/// particular to the match exhaustivity algorithm. +/// +/// The number of rows of the `Matrix` is equal to the number of `PatStack`s and +/// the number of columns of the `Matrix` is equal to the number of elements in +/// the `PatStack`s. Each `PatStack` should contains the same number of +/// elements. +#[derive(Clone, Debug)] +pub(crate) struct Matrix { + rows: Vec, +} + +impl Matrix { + /// Creates an empty `Matrix`. + pub(crate) fn empty() -> Self { + Matrix { rows: vec![] } + } + + /// Creates a `Matrix` with one row from a `PatStack`. + pub(crate) fn from_pat_stack(pat_stack: PatStack) -> Self { + Matrix { + rows: vec![pat_stack], + } + } + + /// Pushes a `PatStack` onto the `Matrix`. + pub(crate) fn push(&mut self, row: PatStack) { + self.rows.push(row); + } + + /// Appends a `Vec` onto the `Matrix`. + pub(crate) fn append(&mut self, rows: &mut Vec) { + self.rows.append(rows); + } + + /// Returns a reference to the rows of the `Matrix`. + pub(crate) fn rows(&self) -> &Vec { + &self.rows + } + + /// Returns the rows of the `Matrix`. + pub(crate) fn into_rows(self) -> Vec { + self.rows + } + + /// Returns the number of rows *m* and the number of columns *n* of the + /// `Matrix` in the form (*m*, *n*). + pub(crate) fn m_n(&self, span: &Span) -> CompileResult<(usize, usize)> { + let warnings = vec![]; + let mut errors = vec![]; + let first = match self.rows.first() { + Some(first) => first, + None => return ok((0, 0), warnings, errors), + }; + let n = first.len(); + for row in self.rows.iter().skip(1) { + if row.len() != n { + errors.push(CompileError::Internal( + "found invalid matrix size", + span.clone(), + )); + return err(warnings, errors); + } + } + ok((self.rows.len(), n), warnings, errors) + } + + /// Reports if the `Matrix` is equivalent to a vector (aka a single + /// `PatStack`). + pub(crate) fn is_a_vector(&self) -> bool { + self.rows.len() == 1 + } + + /// Checks to see if the `Matrix` is a vector, and if it is, returns the + /// single `PatStack` from its elements. + pub(crate) fn unwrap_vector(&self, span: &Span) -> CompileResult { + let warnings = vec![]; + let mut errors = vec![]; + if !self.is_a_vector() { + errors.push(CompileError::Internal( + "found invalid matrix size", + span.clone(), + )); + return err(warnings, errors); + } + match self.rows.first() { + Some(first) => ok(first.clone(), warnings, errors), + None => { + errors.push(CompileError::Internal( + "found invalid matrix size", + span.clone(), + )); + err(warnings, errors) + } + } + } + + /// Computes Σ, where Σ is a `PatStack` containing the first element of + /// every row of the `Matrix`. + pub(crate) fn compute_sigma(&self, span: &Span) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut pat_stack = PatStack::empty(); + for row in self.rows.iter() { + let first = check!( + row.first(span), + return err(warnings, errors), + warnings, + errors + ); + pat_stack.push(first) + } + ok(pat_stack.flatten().filter_out_wildcards(), warnings, errors) + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/mod.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/mod.rs new file mode 100644 index 00000000000..9cf3e72b2a7 --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/mod.rs @@ -0,0 +1,854 @@ +mod constructor_factory; +mod matrix; +mod patstack; +mod pattern; +mod range; +mod witness_report; + +use sway_types::Span; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, MatchCondition, +}; + +use self::{ + constructor_factory::ConstructorFactory, matrix::Matrix, patstack::PatStack, pattern::Pattern, + witness_report::WitnessReport, +}; + +/// Given the arms of a match expression, checks to see if the arms are +/// exhaustive and checks to see if each arm is reachable. +/// +/// --- +/// +/// Modeled after this paper: +/// http://moscova.inria.fr/%7Emaranget/papers/warn/warn004.html +/// +/// Implemented in Rust here: +/// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_mir_build/thir/pattern/usefulness/index.html +/// +/// --- +/// +/// In general, match expressions are constructed as so: +/// +/// ```ignore +/// match value { +/// pattern => result, +/// pattern => result, +/// pattern => result +/// } +/// ``` +/// +/// where `value` is the "matched value", and each `pattern => result` is a +/// "match arm", and `value` will "match" one of the `patterns` in the match +/// arms. A match happens when a `pattern` has the same "type" and "shape" as +/// the `value`, at some level of generality. For example `1` will match `1`, +/// `a`, and `_`, but will not match `2`. +/// +/// The goal of this algorithm is to: +/// 1. Check to see if the arms are exhaustive (i.e. all cases for which the +/// matched value could be are included in the provided arms) +/// 2. Check to see if each arm is reachable (i.e. if each arm is able to +/// "catch" at least on hypothetical matched value without the previous arms +/// "catching" all the values) +/// +/// # `Pattern` +/// +/// A `Pattern` is an object that is able to be matched upon. A `Pattern` is +/// semantically constructed of a "constructor" and its "arguments". For +/// example, given the tuple `(1,2)` "a tuple with 2 elements" is the +/// constructor and "1, 2" are the arguments. Given the u64 `2`, "2" is the +/// constructor and it has no arguments (you can think of this by imagining +/// that u64 is the enum type and each u64 value is a variant of that enum type, +/// making the value itself a constructor). +/// +/// `Pattern`s are semantically categorized into three categories: wildcard +/// patterns (the catchall pattern `_` and variable binding patterns like `a`), +/// constructed patterns (`(1,2)` aka "a tuple with 2 elements" with arguments +/// "1, 2"), and or-patterns (`1 | 2 | .. `). +/// +/// `Pattern`s are used in the exhaustivity algorithm. +/// +/// # Usefulness +/// +/// A pattern is "useful" when it covers at least one case of a possible +/// matched value that had been left uncovered by previous patterns. +/// +/// For example, given: +/// +/// ```ignore +/// let x = true; +/// match x { +/// true => .., +/// false => .. +/// } +/// ``` +/// +/// the pattern `false` is useful because it covers at least one case (i.e. +/// `false`) that had been left uncovered by the previous patterns. +/// +/// Given: +/// +/// ```ignore +/// let x = 5; +/// match x { +/// 0 => .., +/// 1 => .., +/// _ => .. +/// } +/// ``` +/// +/// the pattern `_` is useful because it covers at least one case (i.e. all +/// cases other than 0 and 1) that had been left uncovered by the previous +/// patterns. +/// +/// In another example, given: +/// +/// ```ignore +/// let x = 5; +/// match x { +/// 0 => .., +/// 1 => .., +/// 1 => .., // <-- +/// _ => .. +/// } +/// ``` +/// +/// the pattern `1` (noted with an arrow) is not useful as it does not cover any +/// case that is not already covered by a previous pattern. +/// +/// Given: +/// +/// ```ignore +/// let x = 5; +/// match x { +/// 0 => .., +/// 1 => .., +/// _ => .., +/// 2 => .. // <-- +/// } +/// ``` +/// +/// the pattern `2` is not useful as it does not cover any case that is not +/// already covered by a previous pattern. Even though there is only one pattern +/// `2`, any cases that the pattern `2` covers would previously be caught by the +/// catchall pattern. +/// +/// Usefulness used in the exhaustivity algorithm. +/// +/// # Witnesses +/// +/// A "witness" to a pattern is a concrete example of a matched value that would +/// be caught by that pattern that would not have been caught by previous +/// patterns. +/// +/// For example, given: +/// +/// ```ignore +/// let x = 5; +/// match x { +/// 0 => .., +/// 1 => .., +/// _ => .. +/// } +/// ``` +/// +/// the witness for pattern `1` would be the pattern "1" as the pattern `1` +/// would catch the concrete hypothetical matched value "1" and no other +/// previous cases would have caught it. The witness for pattern `_` is an +/// or-pattern of all of the remaining integers they wouldn't be caught by `0` +/// and `1`, so "3 | .. | MAX". +/// +/// Given: +/// +/// ```ignore +/// let x = 5; +/// match x { +/// 0 => .., +/// 1 => .., +/// 1 => .., // <-- +/// _ => .. +/// } +/// ``` +/// +/// the pattern `1` (noted with an arrow) would not have any witnesses as there +/// that it catches that are not caught by previous patterns. +/// +/// # Putting it all together +/// +/// Given the definitions above, we can say several things: +/// +/// 1. A pattern is useful when it has witnesses to its usefulness (i.e. it has +/// at least one hypothetical value that it catches that is not caught by +/// previous patterns). +/// 2. A match arm is reachable when its pattern is useful. +/// 3. A match expression is exhaustive when, if you add an additional wildcard +/// pattern to the existing patterns, this new wildcard pattern is not +/// useful. +/// +/// # Details +/// +/// This algorithm checks is a match expression is exhaustive and if its match +/// arms are reachable by applying the above definitions of usefulness and +/// witnesses. This algorithm sequentionally creates a `WitnessReport` for every +/// match arm by calling *U(P, q)*, where *P* is the `Matrix` of patterns seen +/// so far and *q* is the current pattern under investigation for its +/// reachability. A match arm is reachable if its `WitnessReport` is non-empty. +/// Once all existing match arms have been analyzed, the match expression is +/// analyzed for its exhaustivity. *U(P, q)* is called again to create another +/// `WitnessReport`, this time where *P* is the `Matrix` of all patterns and `q` +/// is an imaginary additional wildcard pattern. The match expression is +/// exhaustive if the imaginary additional wildcard pattern has an empty +/// `WitnessReport`. +pub(crate) fn check_match_expression_usefulness( + arms: Vec, + span: Span, +) -> CompileResult<(WitnessReport, Vec<(MatchCondition, bool)>)> { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut matrix = Matrix::empty(); + let mut arms_reachability = vec![]; + let factory = ConstructorFactory::new(); + match arms.split_first() { + Some((first_arm, arms_rest)) => { + let pat = check!( + Pattern::from_match_condition(first_arm.clone()), + return err(warnings, errors), + warnings, + errors + ); + matrix.push(PatStack::from_pattern(pat)); + arms_reachability.push((first_arm.clone(), true)); + for arm in arms_rest.iter() { + let pat = check!( + Pattern::from_match_condition(arm.clone()), + return err(warnings, errors), + warnings, + errors + ); + let v = PatStack::from_pattern(pat); + let witness_report = check!( + is_useful(&factory, &matrix, &v, &span), + return err(warnings, errors), + warnings, + errors + ); + matrix.push(v); + // if an arm has witnesses to its usefulness then it is reachable + arms_reachability.push((arm.clone(), witness_report.has_witnesses())); + } + } + None => { + errors.push(CompileError::Internal("empty match arms", span)); + return err(warnings, errors); + } + } + let v = PatStack::from_pattern(Pattern::wild_pattern()); + let witness_report = check!( + is_useful(&factory, &matrix, &v, &span), + return err(warnings, errors), + warnings, + errors + ); + // if a wildcard case has no witnesses to its usefulness, then the match arms are exhaustive + ok((witness_report, arms_reachability), warnings, errors) +} + +/// Given a `Matrix` *P* and a `PatStack` *q*, computes a `WitnessReport` from +/// algorithm *U(P, q)*. +/// +/// This recursive algorithm is basically an induction proof with 2 base cases. +/// The first base case is when *P* is the empty `Matrix`. In this case, we +/// return a witness report where the witnesses are wildcard patterns for every +/// element of *q*. The second base case is when *P* has at least one row but +/// does not have any columns. In this case, we return a witness report with no +/// witnesses. This case indicates exhaustivity. The induction case covers +/// everything else, and what we do for induction depends on what the first +/// element of *q* is. Depending on if the first element of *q* is a wildcard +/// pattern, or-pattern, or constructed pattern we do something different. Each +/// case returns a witness report that we propogate through the recursive steps. +fn is_useful( + factory: &ConstructorFactory, + p: &Matrix, + q: &PatStack, + span: &Span, +) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let (m, n) = check!(p.m_n(span), return err(warnings, errors), warnings, errors); + match (m, n) { + (0, 0) => ok( + WitnessReport::Witnesses(PatStack::fill_wildcards(q.len())), + warnings, + errors, + ), + (_, 0) => ok(WitnessReport::NoWitnesses, warnings, errors), + (_, _) => { + let c = check!( + q.first(span), + return err(warnings, errors), + warnings, + errors + ); + let witness_report = match c { + Pattern::Wildcard => check!( + is_useful_wildcard(factory, p, q, span), + return err(warnings, errors), + warnings, + errors + ), + Pattern::Or(pats) => check!( + is_useful_or(factory, p, q, pats, span), + return err(warnings, errors), + warnings, + errors + ), + c => check!( + is_useful_constructed(factory, p, q, c, span), + return err(warnings, errors), + warnings, + errors + ), + }; + ok(witness_report, warnings, errors) + } + } +} + +/// Computes a witness report from *U(P, q)* when *q* is a wildcard pattern. +/// +/// Because *q* is a wildcard pattern, this means we are checking to see if the +/// wildcard pattern is useful given *P*. We can do this by investigating the +/// first column Σ of *P*. If Σ is a complete signature (that is if Σ contains +/// every constructor for the type of elements in Σ), then we can recursively +/// compute the witnesses for every element of Σ and aggregate them. If Σ is not +/// a complete signature, then we can compute the default `Matrix` for *P* (i.e. +/// a version of *P* that is agnostic to *c*) and recursively compute the +/// witnesses for if q is useful given the new default `Matrix`. +/// +/// --- +/// +/// 1. Compute Σ = {c₁, ... , cₙ}, which is the set of constructors that appear +/// as root constructors of the patterns of *P*'s first column. +/// 2. Determine if Σ is a complete signature. +/// 3. If it is a complete signature: +/// 1. For every every *k* 0..*n*, compute the specialized `Matrix` +/// *S(cₖ, P)* +/// 2. Compute the specialized `Matrix` *S(cₖ, q)* +/// 3. Recursively compute U(S(cₖ, P), S(cₖ, q)) +/// 4. If the recursive call to (3.3) returns a non-empty witness report, +/// create a new pattern from *cₖ* and the witness report and a create a +/// new witness report from the elements not used to create the new +/// pattern +/// 5. Aggregate a new patterns and new witness reports from every call of +/// (3.4) +/// 6. Transform the aggregated patterns from (3.5) into a single pattern +/// and prepend it to the aggregated witness report +/// 7. Return the witness report +/// 4. If it is not a complete signature: +/// 1. Compute the default `Matrix` *D(P)* +/// 2. Compute *q'* as \[q₂ ... qₙ*\]. +/// 3. Recursively compute *U(D(P), q')*. +/// 4. If Σ is empty, create a pattern not present in Σ +/// 5. Add this new pattern to the resulting witness report +/// 6. Return the witness report +fn is_useful_wildcard( + factory: &ConstructorFactory, + p: &Matrix, + q: &PatStack, + span: &Span, +) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + + // 1. Compute Σ = {c₁, ... , cₙ}, which is the set of constructors that appear + // as root constructors of the patterns of *P*'s first column. + let sigma = check!( + p.compute_sigma(span), + return err(warnings, errors), + warnings, + errors + ); + + // 2. Determine if Σ is a complete signature. + let is_complete_signature = check!( + sigma.is_complete_signature(span), + return err(warnings, errors), + warnings, + errors + ); + if is_complete_signature { + // 3. If it is a complete signature: + + let mut witness_report = WitnessReport::NoWitnesses; + let mut pat_stack = PatStack::empty(); + for c_k in sigma.iter() { + // 3.1. For every every *k* 0..*n*, compute the specialized `Matrix` + // *S(cₖ, P)* + let s_c_k_p = check!( + compute_specialized_matrix(c_k, p, span), + return err(warnings, errors), + warnings, + errors + ); + + // 3.2. Compute the specialized `Matrix` *S(cₖ, q)* + let s_c_k_q = check!( + compute_specialized_matrix(c_k, &Matrix::from_pat_stack(q.clone()), span), + return err(warnings, errors), + warnings, + errors + ); + let s_c_k_q = check!( + s_c_k_q.unwrap_vector(span), + return err(warnings, errors), + warnings, + errors + ); + + // 3.3. Recursively compute U(S(cₖ, P), S(cₖ, q)) + let wr = check!( + is_useful(factory, &s_c_k_p, &s_c_k_q, span), + return err(warnings, errors), + warnings, + errors + ); + + // 3.4. If the recursive call to (3.3) returns a non-empty witness report, + // create a new pattern from *cₖ* and the witness report and a create a + // new witness report from the elements not used to create the new + // pattern + // 3.5. Aggregate the new patterns and new witness reports from every call of + // (3.4) + match (&witness_report, wr) { + (WitnessReport::NoWitnesses, WitnessReport::NoWitnesses) => {} + (WitnessReport::NoWitnesses, wr) => { + let (pat, wr) = check!( + WitnessReport::split_into_leading_constructor(wr, c_k, span), + return err(warnings, errors), + warnings, + errors + ); + if !pat_stack.contains(&pat) { + pat_stack.push(pat); + } + witness_report = wr; + } + (_, wr) => { + let (pat, _) = check!( + WitnessReport::split_into_leading_constructor(wr, c_k, span), + return err(warnings, errors), + warnings, + errors + ); + if !pat_stack.contains(&pat) { + pat_stack.push(pat); + } + } + } + } + + // 3.6. Transform the aggregated patterns from (3.5) into a single pattern + // and prepend it to the aggregated witness report + match &mut witness_report { + WitnessReport::NoWitnesses => {} + witness_report => { + let pat_stack = check!( + Pattern::from_pat_stack(pat_stack, span), + return err(warnings, errors), + warnings, + errors + ); + check!( + witness_report.add_witness(pat_stack, span), + return err(warnings, errors), + warnings, + errors + ); + } + } + + // 7. Return the witness report + ok(witness_report, warnings, errors) + } else { + // 4. If it is not a complete signature: + + // 4.1. Compute the default `Matrix` *D(P)* + let d_p = check!( + compute_default_matrix(p, span), + return err(warnings, errors), + warnings, + errors + ); + + // 4.2. Compute *q'* as \[q₂ ... qₙ*\]. + let (_, q_rest) = check!( + q.split_first(span), + return err(warnings, errors), + warnings, + errors + ); + + // 4.3. Recursively compute *U(D(P), q')*. + let mut witness_report = check!( + is_useful(factory, &d_p, &q_rest, span), + return err(warnings, errors), + warnings, + errors + ); + + // 4.4. If Σ is empty, create a pattern not present in Σ + let witness_to_add = if sigma.is_empty() { + Pattern::Wildcard + } else { + check!( + factory.create_pattern_not_present(sigma, span), + return err(warnings, errors), + warnings, + errors + ) + }; + + // 4.5. Add this new pattern to the resulting witness report + match &mut witness_report { + WitnessReport::NoWitnesses => {} + witness_report => check!( + witness_report.add_witness(witness_to_add, span), + return err(warnings, errors), + warnings, + errors + ), + } + + // 4.6. Return the witness report + ok(witness_report, warnings, errors) + } +} + +/// Computes a witness report from *U(P, q)* when *q* is a constructed pattern +/// *c(r₁, ..., rₐ)*. +/// +/// Given a specialized `Matrix` that specializes *P* to *c* and another +/// specialized `Matrix` that specializes *q* to *c*, recursively compute if the +/// latter `Matrix` is useful to the former. +/// +/// --- +/// +/// 1. Extract the specialized `Matrix` *S(c, P)* +/// 2. Extract the specialized `Matrix` *S(c, q)* +/// 3. Recursively compute *U(S(c, P), S(c, q))* +fn is_useful_constructed( + factory: &ConstructorFactory, + p: &Matrix, + q: &PatStack, + c: Pattern, + span: &Span, +) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + + // 1. Extract the specialized `Matrix` *S(c, P)* + let s_c_p = check!( + compute_specialized_matrix(&c, p, span), + return err(warnings, errors), + warnings, + errors + ); + let (s_c_p_m, s_c_p_n) = check!( + s_c_p.m_n(span), + return err(warnings, errors), + warnings, + errors + ); + if s_c_p_m > 0 && s_c_p_n != (c.a() + q.len() - 1) { + errors.push(CompileError::Internal( + "S(c,P) matrix is misshappen", + span.clone(), + )); + return err(warnings, errors); + } + + // 2. Extract the specialized `Matrix` *S(c, q)* + let s_c_q = check!( + compute_specialized_matrix(&c, &Matrix::from_pat_stack(q.clone()), span), + return err(warnings, errors), + warnings, + errors + ); + let s_c_q = check!( + s_c_q.unwrap_vector(span), + return err(warnings, errors), + warnings, + errors + ); + + // 3. Recursively compute *U(S(c, P), S(c, q))* + is_useful(factory, &s_c_p, &s_c_q, span) +} + +/// Computes a witness report from *U(P, q)* when *q* is an or-pattern +/// *(r₁ | ... | rₐ)*. +/// +/// Compute the witness report for each element of q and aggregate them +/// together. +/// +/// --- +/// +/// 1. For each *k* 0..*a* compute *q'* as \[*rₖ q₂ ... qₙ*\]. +/// 2. Compute the witnesses from *U(P, q')* +/// 3. Aggregate the witnesses from every *U(P, q')* +fn is_useful_or( + factory: &ConstructorFactory, + p: &Matrix, + q: &PatStack, + pats: PatStack, + span: &Span, +) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let (_, q_rest) = check!( + q.split_first(span), + return err(warnings, errors), + warnings, + errors + ); + let mut p = p.clone(); + let mut witness_report = WitnessReport::Witnesses(PatStack::empty()); + for pat in pats.into_iter() { + // 1. For each *k* 0..*a* compute *q'* as \[*rₖ q₂ ... qₙ*\]. + let mut v = PatStack::from_pattern(pat); + v.append(&mut q_rest.clone()); + + // 2. Compute the witnesses from *U(P, q')* + let wr = check!( + is_useful(factory, &p, &v, span), + return err(warnings, errors), + warnings, + errors + ); + p.push(v); + + // 3. Aggregate the witnesses from every *U(P, q')* + witness_report = WitnessReport::join_witness_reports(witness_report, wr); + } + ok(witness_report, warnings, errors) +} + +/// Given a `Matrix` *P*, constructs the default `Matrix` *D(P). This is done by +/// sequentially computing the rows of *D(P)*. +/// +/// Intuition: A default `Matrix` is a transformation upon *P* that "shrinks" +/// the rows of *P* depending on if the row is able to generally match all +/// patterns in a default case. +fn compute_default_matrix(p: &Matrix, span: &Span) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut d_p = Matrix::empty(); + for p_i in p.rows().iter() { + d_p.append(&mut check!( + compute_default_matrix_row(p_i, span), + return err(warnings, errors), + warnings, + errors + )); + } + ok(d_p, warnings, errors) +} + +/// Given a `PatStack` *pⁱ* from `Matrix` *P*, compute the resulting row of the +/// default `Matrix` *D(P)*. +/// +/// A row in the default `Matrix` "shrinks itself" or "eliminates itself" +/// depending on if its possible to make general claims the first element of the +/// row *pⁱ₁*. It is possible to make a general claim *pⁱ₁* when *pⁱ₁* is the +/// wildcard pattern (in which case it could match anything) and when *pⁱ₁* is +/// an or-pattern (in which case we can do recursion while pretending that the +/// or-pattern is itself a `Matrix`). A row "eliminates itself" when *pⁱ₁* is a +/// constructed pattern (in which case it could only make a specific constructed +/// pattern and we could not make any general claims about it). +/// +/// --- +/// +/// Rows are defined according to the first component of the row: +/// +/// 1. *pⁱ₁* is a constructed pattern *c'(r₁, ..., rₐ)*: +/// 1. no row is produced +/// 2. *pⁱ₁* is a wildcard pattern: +/// 1. the resulting row equals \[pⁱ₂ ... pⁱₙ*\] +/// 3. *pⁱ₁* is an or-pattern *(r₁ | ... | rₐ)*: +/// 1. Construct a new `Matrix` *P'*, where given *k* 0..*a*, the rows of +/// *P'* are defined as \[*rₖ pⁱ₂ ... pⁱₙ*\] for every *k*. +/// 2. The resulting rows are the rows obtained from calling the recursive +/// *D(P')* +fn compute_default_matrix_row(p_i: &PatStack, span: &Span) -> CompileResult> { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut rows: Vec = vec![]; + let (p_i_1, mut p_i_rest) = check!( + p_i.split_first(span), + return err(warnings, errors), + warnings, + errors + ); + match p_i_1 { + Pattern::Wildcard => { + // 2. *pⁱ₁* is a wildcard pattern: + // 1. the resulting row equals \[pⁱ₂ ... pⁱₙ*\] + let mut row = PatStack::empty(); + row.append(&mut p_i_rest); + rows.push(row); + } + Pattern::Or(pats) => { + // 3. *pⁱ₁* is an or-pattern *(r₁ | ... | rₐ)*: + // 1. Construct a new `Matrix` *P'*, where given *k* 0..*a*, the rows of + // *P'* are defined as \[*rₖ pⁱ₂ ... pⁱₙ*\] for every *k*. + let mut m = Matrix::empty(); + for pat in pats.iter() { + let mut m_row = PatStack::from_pattern(pat.clone()); + m_row.append(&mut p_i_rest.clone()); + m.push(m_row); + } + // 2. The resulting rows are the rows obtained from calling the recursive + // *D(P')* + let d_p = check!( + compute_default_matrix(&m, span), + return err(warnings, errors), + warnings, + errors + ); + rows.append(&mut d_p.into_rows()); + } + // 1. *pⁱ₁* is a constructed pattern *c'(r₁, ..., rₐ)*: + // 1. no row is produced + _ => {} + } + ok(rows, warnings, errors) +} + +/// Given a constructor *c* and a `Matrix` *P*, constructs the specialized +/// `Matrix` *S(c, P)*. This is done by sequentially computing the rows of +/// *S(c, P)*. +/// +/// Intuition: A specialized `Matrix` is a transformation upon *P* that +/// "unwraps" the rows of *P* depending on if they are congruent with *c*. +fn compute_specialized_matrix(c: &Pattern, p: &Matrix, span: &Span) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut s_c_p = Matrix::empty(); + for p_i in p.rows().iter() { + s_c_p.append(&mut check!( + compute_specialized_matrix_row(c, p_i, span), + return err(warnings, errors), + warnings, + errors + )); + } + let (m, _) = check!( + s_c_p.m_n(span), + return err(warnings, errors), + warnings, + errors + ); + if p.is_a_vector() && m > 1 { + errors.push(CompileError::Internal( + "S(c,p) must be a vector", + span.clone(), + )); + return err(warnings, errors); + } + ok(s_c_p, warnings, errors) +} + +/// Given a constructor *c* and a `PatStack` *pⁱ* from `Matrix` *P*, compute the +/// resulting row of the specialized `Matrix` *S(c, P)*. +/// +/// Intuition: a row in the specialized `Matrix` "expands itself" or "eliminates +/// itself" depending on if its possible to furthur "drill down" into the +/// elements of *P* given a *c* that we are specializing for. It is possible to +/// "drill down" when the first element of a row of *P* *pⁱ₁* matches *c* (in +/// which case it is possible to "drill down" into the arguments for *pⁱ₁*), +/// when *pⁱ₁* is the wildcard case (in which case it is possible to "drill +/// down" into "fake" arguments for *pⁱ₁* as it does not matter if *c* matches +/// or not), and when *pⁱ₁* is an or-pattern (in which case we can do recursion +/// while pretending that the or-pattern is itself a `Matrix`). A row +/// "eliminates itself" when *pⁱ₁* does not match *c* (in which case it is not +/// possible to "drill down"). +/// +/// --- +/// +/// Rows are defined according to the first component of the row: +/// +/// 1. *pⁱ₁* is a constructed pattern *c'(r₁, ..., rₐ)* where *c* == *c'*: +/// 1. the resulting row equals \[*r₁ ... rₐ pⁱ₂ ... pⁱₙ*\] +/// 2. *pⁱ₁* is a constructed pattern *c'(r₁, ..., rₐ)* where *c* != *c'*: +/// 1. no row is produced +/// 3. *pⁱ₁* is a wildcard pattern and the number of sub-patterns in *c* is *a*: +/// 1. the resulting row equals \[*_₁ ... _ₐ pⁱ₂ ... pⁱₙ*\] +/// 4. *pⁱ₁* is an or-pattern *(r₁ | ... | rₐ)*: +/// 1. Construct a new `Matrix` *P'* where, given *k* 0..*a*, the rows of +/// *P'* are defined as \[*rₖ pⁱ₂ ... pⁱₙ*\] for every *k* +/// 2. The resulting rows are the rows obtained from calling the recursive +/// *S(c, P')* +fn compute_specialized_matrix_row( + c: &Pattern, + p_i: &PatStack, + span: &Span, +) -> CompileResult> { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut rows: Vec = vec![]; + let (p_i_1, mut p_i_rest) = check!( + p_i.split_first(span), + return err(warnings, errors), + warnings, + errors + ); + match p_i_1 { + Pattern::Wildcard => { + // 3. *pⁱ₁* is a wildcard pattern and the number of sub-patterns in *c* is *a*: + // 3.1. the resulting row equals \[*_₁ ... _ₐ pⁱ₂ ... pⁱₙ*\] + let mut row: PatStack = PatStack::fill_wildcards(c.a()); + row.append(&mut p_i_rest); + rows.push(row); + } + Pattern::Or(pats) => { + // 4. *pⁱ₁* is an or-pattern *(r₁ | ... | rₐ)*: + // 4.1. Construct a new `Matrix` *P'* where, given *k* 0..*a*, the rows of + // *P'* are defined as \[*rₖ pⁱ₂ ... pⁱₙ*\] for every *k* + let mut m = Matrix::empty(); + for pat in pats.iter() { + let mut m_row = PatStack::from_pattern(pat.clone()); + m_row.append(&mut p_i_rest.clone()); + m.push(m_row); + } + + // 4.2. The resulting rows are the rows obtained from calling the recursive + // *S(c, P')* + let s_c_p = check!( + compute_specialized_matrix(c, &m, span), + return err(warnings, errors), + warnings, + errors + ); + rows.append(&mut s_c_p.into_rows()); + } + other => { + if c.has_the_same_constructor(&other) { + // 1. *pⁱ₁* is a constructed pattern *c'(r₁, ..., rₐ)* where *c* == *c'*: + // 1.1. the resulting row equals \[*r₁ ... rₐ pⁱ₂ ... pⁱₙ*\] + let mut row: PatStack = check!( + other.sub_patterns(span), + return err(warnings, errors), + warnings, + errors + ); + row.append(&mut p_i_rest); + rows.push(row); + } + // 2. *pⁱ₁* is a constructed pattern *c'(r₁, ..., rₐ)* where *c* != *c'*: + // 2.1. no row is produced + } + } + ok(rows, warnings, errors) +} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/patstack.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/patstack.rs new file mode 100644 index 00000000000..3635a884f47 --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/patstack.rs @@ -0,0 +1,503 @@ +use std::{fmt, slice::Iter, vec::IntoIter}; + +use itertools::Itertools; +use sway_types::Span; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, +}; + +use super::{pattern::Pattern, range::Range}; + +/// A `PatStack` is a `Vec` that is implemented with special methods +/// particular to the match exhaustivity algorithm. +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct PatStack { + pats: Vec, +} + +impl PatStack { + /// Creates an empty `PatStack`. + pub(crate) fn empty() -> Self { + PatStack { pats: vec![] } + } + + /// Given a `Pattern` *p*, creates a `PatStack` with one element *p*. + pub(crate) fn from_pattern(p: Pattern) -> Self { + PatStack { pats: vec![p] } + } + + /// Given a usize *n*, creates a `PatStack` filled with *n* + /// `Pattern::Wildcard` elements. + pub(crate) fn fill_wildcards(n: usize) -> Self { + let mut pats = vec![]; + for _ in 0..n { + pats.push(Pattern::Wildcard); + } + PatStack { pats } + } + + /// Returns the first element of a `PatStack`. + pub(crate) fn first(&self, span: &Span) -> CompileResult { + let warnings = vec![]; + let mut errors = vec![]; + match self.pats.first() { + Some(first) => ok(first.to_owned(), warnings, errors), + None => { + errors.push(CompileError::Internal("empty PatStack", span.clone())); + err(warnings, errors) + } + } + } + + /// Returns a tuple of the first element of a `PatStack` and the rest of the + /// elements. + pub(crate) fn split_first(&self, span: &Span) -> CompileResult<(Pattern, PatStack)> { + let warnings = vec![]; + let mut errors = vec![]; + match self.pats.split_first() { + Some((first, pat_stack_contents)) => { + let pat_stack = PatStack { + pats: pat_stack_contents.to_vec(), + }; + ok((first.to_owned(), pat_stack), warnings, errors) + } + None => { + errors.push(CompileError::Internal("empty PatStack", span.clone())); + err(warnings, errors) + } + } + } + + /// Given a usize *n*, splits the `PatStack` at *n* and returns both halves. + pub(crate) fn split_at(&self, n: usize, span: &Span) -> CompileResult<(PatStack, PatStack)> { + let warnings = vec![]; + let mut errors = vec![]; + if n > self.len() { + errors.push(CompileError::Internal( + "attempting to split OOB", + span.clone(), + )); + return err(warnings, errors); + } + let (a, b) = self.pats.split_at(n); + let x = PatStack { pats: a.to_vec() }; + let y = PatStack { pats: b.to_vec() }; + ok((x, y), warnings, errors) + } + + /// Pushes a `Pattern` onto the `PatStack` + pub(crate) fn push(&mut self, other: Pattern) { + self.pats.push(other) + } + + /// Given a usize *n*, returns a mutable reference to the `PatStack` at + /// index *n*. + fn get_mut(&mut self, n: usize, span: &Span) -> CompileResult<&mut Pattern> { + let warnings = vec![]; + let mut errors = vec![]; + match self.pats.get_mut(n) { + Some(elem) => ok(elem, warnings, errors), + None => { + errors.push(CompileError::Internal( + "cant retrieve mutable reference to element", + span.clone(), + )); + err(warnings, errors) + } + } + } + + /// Appends a `PatStack` onto the `PatStack`. + pub(crate) fn append(&mut self, others: &mut PatStack) { + self.pats.append(&mut others.pats); + } + + /// Prepends a `Pattern` onto the `PatStack`. + pub(crate) fn prepend(&mut self, other: Pattern) { + self.pats.insert(0, other); + } + + /// Returns the length of the `PatStack`. + pub(crate) fn len(&self) -> usize { + self.pats.len() + } + + /// Reports if the `PatStack` is empty. + pub(crate) fn is_empty(&self) -> bool { + self.flatten().filter_out_wildcards().pats.is_empty() + } + + /// Reports if the `PatStack` contains a given `Pattern`. + pub(crate) fn contains(&self, pat: &Pattern) -> bool { + self.pats.contains(pat) + } + + /// Reports if the `PatStack` contains an or-pattern at the top level. + fn contains_or_pattern(&self) -> bool { + for pat in self.pats.iter() { + if let Pattern::Or(_) = pat { + return true; + } + } + false + } + + pub(crate) fn iter(&self) -> Iter<'_, Pattern> { + self.pats.iter() + } + + pub(crate) fn into_iter(self) -> IntoIter { + self.pats.into_iter() + } + + /// Reports if the `PatStack` Σ is a "complete signature" of the type of the + /// elements of Σ. + /// + /// For example, a Σ composed of `Pattern::U64(..)`s would need to check for + /// if it is a complete signature for the `U64` pattern type. Versus a Σ + /// composed of `Pattern::Tuple([.., ..])` which would need to check for if + /// it is a complete signature for "`Tuple` with 2 sub-patterns" type. + /// + /// There are several rules with which to determine if Σ is a complete + /// signature: + /// + /// 1. If Σ is empty it is not a complete signature. + /// 2. If Σ contains only wildcard patterns, it is not a complete signature. + /// 3. If Σ contains all constructors for the type of the elements of Σ then + /// it is a complete signature. + /// + /// For example, given this Σ: + /// + /// ```ignore + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 7, last: 7 }) + /// ] + /// ``` + /// + /// this would not be a complete signature as it does not contain all + /// elements from the `U64` type. + /// + /// Given this Σ: + /// + /// ```ignore + /// [ + /// Pattern::U64(Range { first: std::u64::MIN, last: std::u64::MAX }) + /// ] + /// ``` + /// + /// this would be a complete signature as it does contain all elements from + /// the `U64` type. + /// + /// Given this Σ: + /// + /// ```ignore + /// [ + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::Wildcard + /// ]), + /// ] + /// ``` + /// + /// this would also be a complete signature as it does contain all elements + /// from the "`Tuple` with 2 sub-patterns" type. + pub(crate) fn is_complete_signature(&self, span: &Span) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let preprocessed = self.flatten().filter_out_wildcards(); + if preprocessed.pats.is_empty() { + return ok(false, warnings, errors); + } + let (first, rest) = check!( + preprocessed.split_first(span), + return err(warnings, errors), + warnings, + errors + ); + match first { + // its assumed that no one is ever going to list every string + Pattern::String(_) => ok(false, warnings, errors), + // its assumed that no one is ever going to list every B256 + Pattern::B256(_) => ok(false, warnings, errors), + Pattern::U8(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U8(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + Range::do_ranges_equal_range(ranges, Range::u8(), span) + } + Pattern::U16(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U16(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + Range::do_ranges_equal_range(ranges, Range::u16(), span) + } + Pattern::U32(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U32(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + Range::do_ranges_equal_range(ranges, Range::u32(), span) + } + Pattern::U64(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::U64(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + Range::do_ranges_equal_range(ranges, Range::u64(), span) + } + Pattern::Byte(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::Byte(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + Range::do_ranges_equal_range(ranges, Range::u8(), span) + } + Pattern::Numeric(range) => { + let mut ranges = vec![range]; + for pat in rest.into_iter() { + match pat { + Pattern::Numeric(range) => ranges.push(range), + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + Range::do_ranges_equal_range(ranges, Range::u64(), span) + } + Pattern::Boolean(b) => { + let mut true_found = false; + let mut false_found = false; + match b { + true => true_found = true, + false => false_found = true, + } + for pat in rest.iter() { + match pat { + Pattern::Boolean(b) => match b { + true => true_found = true, + false => false_found = true, + }, + _ => { + errors.push(CompileError::Internal("type mismatch", span.clone())); + return err(warnings, errors); + } + } + } + ok(true_found && false_found, warnings, errors) + } + ref tup @ Pattern::Tuple(_) => { + for pat in rest.iter() { + if !pat.has_the_same_constructor(tup) { + return ok(false, warnings, errors); + } + } + ok(true, warnings, errors) + } + ref strct @ Pattern::Struct(_) => { + for pat in rest.iter() { + if !pat.has_the_same_constructor(strct) { + return ok(false, warnings, errors); + } + } + ok(true, warnings, errors) + } + Pattern::Wildcard => unreachable!(), + Pattern::Or(_) => unreachable!(), + } + } + + /// Flattens the contents of a `PatStack` into a `PatStack`. + pub(crate) fn flatten(&self) -> PatStack { + let mut flattened = PatStack::empty(); + for pat in self.pats.iter() { + flattened.append(&mut pat.flatten()); + } + flattened + } + + /// Returns the given `PatStack` with wildcard patterns filtered out. + pub(crate) fn filter_out_wildcards(&self) -> PatStack { + let mut pats = PatStack::empty(); + for pat in self.pats.iter() { + match pat { + Pattern::Wildcard => {} + pat => pats.push(pat.to_owned()), + } + } + pats + } + + /// Given a `PatStack` *args*, return a `Vec` *args*' + /// "serialized" from *args*. + /// + /// Or-patterns are extracted to create a vec of `PatStack`s *args*' where + /// each `PatStack` is a copy of *args* where the index of the or-pattern is + /// instead replaced with one element from the or-patterns contents. More + /// specifically, given an *args* with one or-pattern that contains n + /// elements, this "serialization" would result in *args*' of length n. + /// Given an *args* with two or-patterns that contain n elements and m + /// elements, this would result in *args*' of length n*m. + /// + /// For example, given an *args*: + /// + /// ```ignore + /// [ + /// Pattern::Or([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ]), + /// Pattern::Wildcard + /// ] + /// ``` + /// + /// *args* would serialize to: + /// + /// ```ignore + /// [ + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::Wildcard + /// ], + /// [ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::Wildcard + /// ] + /// ] + /// ``` + /// + /// Given an *args*: + /// + /// ```ignore + /// [ + /// Pattern::Or([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ]), + /// Pattern::Or([ + /// Pattern::U64(Range { first: 2, last: 2 }), + /// Pattern::U64(Range { first: 3, last: 3 }), + /// Pattern::U64(Range { first: 4, last: 4 }), + /// ]), + /// ] + /// ``` + /// + /// *args* would serialize to: + /// + /// ```ignore + /// [ + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 2, last: 2 }) + /// ], + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 3, last: 3 }) + /// ], + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 4, last: 4 }) + /// ], + /// [ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::U64(Range { first: 2, last: 2 }) + /// ], + /// [ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::U64(Range { first: 3, last: 3 }) + /// ], + /// [ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::U64(Range { first: 4, last: 4 }) + /// ], + /// ] + /// ``` + pub(crate) fn serialize_multi_patterns(self, span: &Span) -> CompileResult> { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut output: Vec = vec![]; + let mut stack: Vec = vec![self]; + while !stack.is_empty() { + let top = match stack.pop() { + Some(top) => top, + None => { + errors.push(CompileError::Internal("can't pop Vec", span.clone())); + return err(warnings, errors); + } + }; + if !top.contains_or_pattern() { + output.push(top); + } else { + for (i, pat) in top.clone().into_iter().enumerate() { + if let Pattern::Or(elems) = pat { + for elem in elems.into_iter() { + let mut top = top.clone(); + let r = check!( + top.get_mut(i, span), + return err(warnings, errors), + warnings, + errors + ); + let _ = std::mem::replace(r, elem); + stack.push(top); + } + } + } + } + } + output.reverse(); + ok(output, warnings, errors) + } +} + +impl From> for PatStack { + fn from(pats: Vec) -> Self { + PatStack { pats } + } +} + +impl fmt::Display for PatStack { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = self + .flatten() + .into_iter() + .map(|x| format!("{}", x)) + .join(", "); + write!(f, "{}", s) + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/pattern.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/pattern.rs new file mode 100644 index 00000000000..36b75c0fea6 --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/pattern.rs @@ -0,0 +1,711 @@ +use std::fmt; + +use itertools::Itertools; +use sway_types::{Ident, Span}; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, Literal, MatchCondition, Scrutinee, StructScrutineeField, +}; + +use super::{patstack::PatStack, range::Range}; + +/// A `Pattern` represents something that could be on the LHS of a match +/// expression arm. +/// +/// For instance this match expression: +/// +/// ```ignore +/// let x = (0, 5); +/// match x { +/// (0, 1) => true, +/// (2, 3) => true, +/// _ => false +/// } +/// ``` +/// +/// would result in these patterns: +/// +/// ```ignore +/// Pattern::Tuple([ +/// Pattern::U64(Range { first: 0, last: 0 }), +/// Pattern::U64(Range { first: 1, last: 1 }) +/// ]) +/// Pattern::Tuple([ +/// Pattern::U64(Range { first: 2, last: 2 }), +/// Pattern::U64(Range { first: 3, last: 3 }) +/// ]) +/// Pattern::Wildcard +/// ``` +/// +/// --- +/// +/// A `Pattern` is semantically constructed from a "constructor" and its +/// "arguments." Given the `Pattern`: +/// +/// ```ignore +/// Pattern::Tuple([ +/// Pattern::U64(Range { first: 0, last: 0 }), +/// Pattern::U64(Range { first: 1, last: 1 }) +/// ]) +/// ``` +/// +/// the constructor is: +/// +/// ```ignore +/// Pattern::Tuple([.., ..]) +/// ``` +/// +/// and the arguments are: +/// +/// ```ignore +/// [ +/// Pattern::U64(Range { first: 0, last: 0 }), +/// Pattern::U64(Range { first: 1, last: 1 }) +/// ] +/// ``` +/// +/// Given the `Pattern`: +/// +/// ```ignore +/// Pattern::U64(Range { first: 0, last: 0 }) +/// ``` +/// +/// the constructor is: +/// +/// ```ignore +/// Pattern::U64(Range { first: 0, last: 0 }) +/// ``` +/// and the arguments are empty. More specifically, in the case of u64 (and +/// other numbers), we can think of u64 as a giant enum, where every u64 value +/// is one variant of the enum, and each of these variants maps to a `Pattern`. +/// So "2u64" can be mapped to a `Pattern` with the constructor "2u64" +/// (represented as a `Range`) and with empty arguments. +/// +/// This idea of a constructor and arguments is used in the match exhaustivity +/// algorithm. +/// +/// --- +/// +/// The variants of `Pattern` can be semantically categorized into 3 categories: +/// +/// 1. the wildcard pattern (Pattern::Wildcard) +/// 2. the or pattern (Pattern::Or(..)) +/// 3. constructed patterns (everything else) +/// +/// This idea of semantic categorization is used in the match exhaustivity +/// algorithm. +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum Pattern { + Wildcard, + U8(Range), + U16(Range), + U32(Range), + U64(Range), + B256([u8; 32]), + Boolean(bool), + Byte(Range), + Numeric(Range), + String(String), + Struct(StructPattern), + Tuple(PatStack), + Or(PatStack), +} + +impl Pattern { + /// Converts a `MatchCondition` to a `Pattern`. + pub(crate) fn from_match_condition(match_condition: MatchCondition) -> CompileResult { + match match_condition { + MatchCondition::CatchAll(_) => ok(Pattern::Wildcard, vec![], vec![]), + MatchCondition::Scrutinee(scrutinee) => Pattern::from_scrutinee(scrutinee), + } + } + + /// Converts a `Scrutinee` to a `Pattern`. + fn from_scrutinee(scrutinee: Scrutinee) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + match scrutinee { + Scrutinee::Variable { .. } => ok(Pattern::Wildcard, warnings, errors), + Scrutinee::Literal { value, .. } => match value { + Literal::U8(x) => ok(Pattern::U8(Range::from_single(x)), warnings, errors), + Literal::U16(x) => ok(Pattern::U16(Range::from_single(x)), warnings, errors), + Literal::U32(x) => ok(Pattern::U32(Range::from_single(x)), warnings, errors), + Literal::U64(x) => ok(Pattern::U64(Range::from_single(x)), warnings, errors), + Literal::B256(x) => ok(Pattern::B256(x), warnings, errors), + Literal::Boolean(b) => ok(Pattern::Boolean(b), warnings, errors), + Literal::Byte(x) => ok(Pattern::Byte(Range::from_single(x)), warnings, errors), + Literal::Numeric(x) => { + ok(Pattern::Numeric(Range::from_single(x)), warnings, errors) + } + Literal::String(s) => ok(Pattern::String(s.as_str().to_string()), warnings, errors), + }, + Scrutinee::StructScrutinee { + struct_name, + fields, + .. + } => { + let mut new_fields = vec![]; + for StructScrutineeField { + field, scrutinee, .. + } in fields.into_iter() + { + let f = match scrutinee { + Some(scrutinee) => check!( + Pattern::from_scrutinee(scrutinee), + return err(warnings, errors), + warnings, + errors + ), + None => Pattern::Wildcard, + }; + new_fields.push((field.as_str().to_string(), f)); + } + let pat = Pattern::Struct(StructPattern { + struct_name, + fields: new_fields, + }); + ok(pat, warnings, errors) + } + Scrutinee::Tuple { elems, .. } => { + let mut new_elems = PatStack::empty(); + for elem in elems.into_iter() { + new_elems.push(check!( + Pattern::from_scrutinee(elem), + return err(warnings, errors), + warnings, + errors + )); + } + ok(Pattern::Tuple(new_elems), warnings, errors) + } + Scrutinee::Unit { span } => { + errors.push(CompileError::Unimplemented( + "unit exhaustivity checking", + span, + )); + err(warnings, errors) + } + Scrutinee::EnumScrutinee { span, .. } => { + errors.push(CompileError::Unimplemented( + "enum exhaustivity checking", + span, + )); + err(warnings, errors) + } + } + } + + /// Converts a `PatStack` to a `Pattern`. If the `PatStack` is of lenth 1, + /// this function returns the single element, if it is of length > 1, this + /// function wraps the provided `PatStack` in a `Pattern::Or(..)`. + pub(crate) fn from_pat_stack(pat_stack: PatStack, span: &Span) -> CompileResult { + if pat_stack.len() == 1 { + pat_stack.first(span) + } else { + ok(Pattern::Or(pat_stack), vec![], vec![]) + } + } + + /// Given a `Pattern` *c* and a `PatStack` *args*, extracts the constructor + /// from *c* and applies it to *args*. For example, given: + /// + /// ```ignore + /// c: Pattern::Tuple([ + /// Pattern::U64(Range { first: 5, last: 7, }), + /// Pattern::U64(Range { first: 10, last: 12 }) + /// ]) + /// args: [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ] + /// ``` + /// + /// the extracted constructor *ctor* from *c* would be: + /// + /// ```ignore + /// Pattern::Tuple([.., ..]) + /// ``` + /// + /// Applying *args* to *ctor* would give: + /// + /// ```ignore + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ]) + /// ``` + /// + /// --- + /// + /// If if is the case that at lease one element of *args* is a + /// or-pattern, then *args* is first "serialized". Meaning, that all + /// or-patterns are extracted to create a vec of `PatStack`s *args*' where + /// each `PatStack` is a copy of *args* where the index of the or-pattern is + /// instead replaced with one element from the or-patterns contents. More + /// specifically, given an *args* with one or-pattern that contains n + /// elements, this "serialization" would result in *args*' of length n. + /// Given an *args* with two or-patterns that contain n elements and m + /// elements, this would result in *args*' of length n*m. + /// + /// Once *args*' is constructed, *ctor* is applied to every element of + /// *args*' and the resulting `Pattern`s are wrapped inside of an + /// or-pattern. + /// + /// For example, given: + /// + /// ```ignore + /// ctor: Pattern::Tuple([.., ..]) + /// args: [ + /// Pattern::Or([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ]), + /// Pattern::Wildcard + /// ] + /// ``` + /// + /// *args* would serialize to: + /// + /// ```ignore + /// [ + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::Wildcard + /// ], + /// [ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::Wildcard + /// ] + /// ] + /// ``` + /// + /// applying *ctor* would create: + /// + /// ```ignore + /// [ + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::Wildcard + /// ]), + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::Wildcard + /// ]), + /// ] + /// ``` + /// + /// and wrapping this in an or-pattern would create: + /// + /// ```ignore + /// Pattern::Or([ + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::Wildcard + /// ]), + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 1, last: 1 }), + /// Pattern::Wildcard + /// ]), + /// ]) + /// ``` + pub(crate) fn from_constructor_and_arguments( + c: &Pattern, + args: PatStack, + span: &Span, + ) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let pat = match c { + Pattern::Wildcard => unreachable!(), + Pattern::U8(range) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::U8(range.clone()) + } + Pattern::U16(range) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::U16(range.clone()) + } + Pattern::U32(range) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::U32(range.clone()) + } + Pattern::U64(range) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::U64(range.clone()) + } + Pattern::B256(b) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::B256(*b) + } + Pattern::Boolean(b) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::Boolean(*b) + } + Pattern::Byte(range) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::Byte(range.clone()) + } + Pattern::Numeric(range) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::Numeric(range.clone()) + } + Pattern::String(s) => { + if !args.is_empty() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + Pattern::String(s.clone()) + } + Pattern::Struct(struct_pattern) => { + if args.len() != struct_pattern.fields.len() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + let pats: PatStack = check!( + args.serialize_multi_patterns(span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(|args| { + Pattern::Struct(StructPattern { + struct_name: struct_pattern.struct_name.clone(), + fields: struct_pattern + .fields + .iter() + .zip(args.into_iter()) + .map(|((name, _), arg)| (name.clone(), arg)) + .collect::>(), + }) + }) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(pats, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::Tuple(elems) => { + if elems.len() != args.len() { + errors.push(CompileError::Internal( + "malformed constructor request", + span.clone(), + )); + return err(warnings, errors); + } + let pats: PatStack = check!( + args.serialize_multi_patterns(span), + return err(warnings, errors), + warnings, + errors + ) + .into_iter() + .map(Pattern::Tuple) + .collect::>() + .into(); + check!( + Pattern::from_pat_stack(pats, span), + return err(warnings, errors), + warnings, + errors + ) + } + Pattern::Or(_) => unreachable!(), + }; + ok(pat, warnings, errors) + } + + /// Create a `Pattern::Wildcard` + pub(crate) fn wild_pattern() -> Self { + Pattern::Wildcard + } + + /// Finds the "a value" of the `Pattern`, AKA the number of sub-patterns + /// used in the pattern's constructor. For example, the pattern + /// `Pattern::Tuple([.., ..])` would have an "a value" of 2. + pub(crate) fn a(&self) -> usize { + match self { + Pattern::U8(_) => 0, + Pattern::U16(_) => 0, + Pattern::U32(_) => 0, + Pattern::U64(_) => 0, + Pattern::B256(_) => 0, + Pattern::Boolean(_) => 0, + Pattern::Byte(_) => 0, + Pattern::Numeric(_) => 0, + Pattern::String(_) => 0, + Pattern::Struct(StructPattern { fields, .. }) => fields.len(), + Pattern::Tuple(elems) => elems.len(), + Pattern::Wildcard => unreachable!(), + Pattern::Or(_) => unreachable!(), + } + } + + /// Checks to see if two `Pattern` have the same constructor. For example, + /// given the patterns: + /// + /// ```ignore + /// A: Pattern::U64(Range { first: 0, last: 0 }) + /// B: Pattern::U64(Range { first: 0, last: 0 }) + /// C: Pattern::U64(Range { first: 1, last: 1 }) + /// ``` + /// + /// A and B have the same constructor but A and C do not. + /// + /// Given the patterns: + /// + /// ```ignore + /// A: Pattern::Tuple([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }), + /// ]) + /// B: Pattern::Tuple([ + /// Pattern::U64(Range { first: 2, last: 2 }), + /// Pattern::U64(Range { first: 3, last: 3 }), + /// ]) + /// C: Pattern::Tuple([ + /// Pattern::U64(Range { first: 4, last: 4 }), + /// ]) + /// ``` + /// + /// A and B have the same constructor but A and C do not. + pub(crate) fn has_the_same_constructor(&self, other: &Pattern) -> bool { + match (self, other) { + (Pattern::Wildcard, Pattern::Wildcard) => true, + (Pattern::U8(a), Pattern::U8(b)) => a == b, + (Pattern::U16(a), Pattern::U16(b)) => a == b, + (Pattern::U32(a), Pattern::U32(b)) => a == b, + (Pattern::U64(a), Pattern::U64(b)) => a == b, + (Pattern::B256(x), Pattern::B256(y)) => x == y, + (Pattern::Boolean(x), Pattern::Boolean(y)) => x == y, + (Pattern::Byte(a), Pattern::Byte(b)) => a == b, + (Pattern::Numeric(a), Pattern::Numeric(b)) => a == b, + (Pattern::String(x), Pattern::String(y)) => x == y, + ( + Pattern::Struct(StructPattern { + struct_name: struct_name1, + fields: fields1, + }), + Pattern::Struct(StructPattern { + struct_name: struct_name2, + fields: fields2, + }), + ) => struct_name1 == struct_name2 && fields1.len() == fields2.len(), + (Pattern::Tuple(elems1), Pattern::Tuple(elems2)) => elems1.len() == elems2.len(), + (Pattern::Or(_), Pattern::Or(_)) => unreachable!(), + _ => false, + } + } + + /// Extracts the "sub-patterns" of a `Pattern`, aka the "arguments" to the + /// patterns "constructor". Some patterns have 0 sub-patterns and some + /// patterns have >0 sub-patterns. For example, this pattern: + /// + /// ```ignore + /// Pattern::U64(Range { first: 0, last: 0 }), + /// ``` + /// + /// has 0 sub-patterns. While this pattern: + /// + /// ```ignore + /// Pattern::Tuple([ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ]) + /// ``` + /// + /// has 2 sub-patterns: + /// + /// ```ignore + /// [ + /// Pattern::U64(Range { first: 0, last: 0 }), + /// Pattern::U64(Range { first: 1, last: 1 }) + /// ] + /// ``` + pub(crate) fn sub_patterns(&self, span: &Span) -> CompileResult { + let warnings = vec![]; + let mut errors = vec![]; + let pats = match self { + Pattern::Struct(StructPattern { fields, .. }) => fields + .iter() + .map(|(_, field)| field.to_owned()) + .collect::>() + .into(), + Pattern::Tuple(elems) => elems.to_owned(), + _ => PatStack::empty(), + }; + if self.a() != pats.len() { + errors.push(CompileError::Internal( + "invariant self.a() == pats.len() broken", + span.clone(), + )); + return err(warnings, errors); + } + ok(pats, warnings, errors) + } + + /// Flattens a `Pattern` into a `PatStack`. If the pattern is an + /// "or-pattern", return its contents, otherwise return the pattern as a + /// `PatStack` + pub(crate) fn flatten(&self) -> PatStack { + match self { + Pattern::Or(pats) => pats.to_owned(), + pat => PatStack::from_pattern(pat.to_owned()), + } + } +} + +impl fmt::Display for Pattern { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let s = match self { + Pattern::Wildcard => "_".to_string(), + Pattern::U8(range) => format!("{}", range), + Pattern::U16(range) => format!("{}", range), + Pattern::U32(range) => format!("{}", range), + Pattern::U64(range) => format!("{}", range), + Pattern::Numeric(range) => format!("{}", range), + Pattern::B256(n) => format!("{:#?}", n), + Pattern::Boolean(b) => format!("{}", b), + Pattern::Byte(range) => format!("{}", range), + Pattern::String(s) => s.clone(), + Pattern::Struct(struct_pattern) => format!("{}", struct_pattern), + Pattern::Tuple(elems) => { + let mut builder = String::new(); + builder.push('('); + builder.push_str(&format!("{}", elems)); + builder.push(')'); + builder + } + Pattern::Or(_) => unreachable!(), + }; + write!(f, "{}", s) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub(crate) struct StructPattern { + struct_name: Ident, + fields: Vec<(String, Pattern)>, +} + +impl StructPattern { + pub(crate) fn new(struct_name: Ident, fields: Vec<(String, Pattern)>) -> Self { + StructPattern { + struct_name, + fields, + } + } + + pub(crate) fn struct_name(&self) -> &Ident { + &self.struct_name + } + + pub(crate) fn fields(&self) -> &Vec<(String, Pattern)> { + &self.fields + } +} + +impl fmt::Display for StructPattern { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut builder = String::new(); + builder.push_str(self.struct_name.as_str()); + builder.push_str(" { "); + let mut start_of_wildcard_tail = None; + for (i, (_, pat)) in self.fields.iter().enumerate().rev() { + match (pat, start_of_wildcard_tail) { + (Pattern::Wildcard, None) => {} + (_, None) => start_of_wildcard_tail = Some(i + 1), + (_, _) => {} + } + } + let s: String = match start_of_wildcard_tail { + Some(start_of_wildcard_tail) => { + let (front, _) = self.fields.split_at(start_of_wildcard_tail); + let mut inner_builder = front + .iter() + .map(|(name, field)| { + let mut inner_builder = String::new(); + inner_builder.push_str(name); + inner_builder.push_str(": "); + inner_builder.push_str(&format!("{}", field)); + inner_builder + }) + .join(", "); + inner_builder.push_str(", ..."); + inner_builder + } + None => self + .fields + .iter() + .map(|(name, field)| { + let mut inner_builder = String::new(); + inner_builder.push_str(name); + inner_builder.push_str(": "); + inner_builder.push_str(&format!("{}", field)); + inner_builder + }) + .join(", "), + }; + builder.push_str(&s); + builder.push_str(" }"); + write!(f, "{}", builder) + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/range.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/range.rs new file mode 100644 index 00000000000..47662a0580e --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/range.rs @@ -0,0 +1,567 @@ +use std::{ + fmt::{self, Debug}, + ops::Sub, +}; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, +}; +use itertools::Itertools; +use sway_types::Span; + +pub(crate) trait MyMath { + fn global_max() -> T; + fn global_min() -> T; + + fn incr(&self) -> T; + fn decr(&self) -> T; +} + +impl MyMath for u8 { + fn global_max() -> u8 { + std::u8::MAX + } + fn global_min() -> u8 { + std::u8::MIN + } + + fn incr(&self) -> u8 { + self + 1 + } + fn decr(&self) -> u8 { + self - 1 + } +} + +impl MyMath for u16 { + fn global_max() -> u16 { + std::u16::MAX + } + fn global_min() -> u16 { + std::u16::MIN + } + + fn incr(&self) -> u16 { + self + 1 + } + fn decr(&self) -> u16 { + self - 1 + } +} + +impl MyMath for u32 { + fn global_max() -> u32 { + std::u32::MAX + } + fn global_min() -> u32 { + std::u32::MIN + } + + fn incr(&self) -> u32 { + self + 1 + } + fn decr(&self) -> u32 { + self - 1 + } +} + +impl MyMath for u64 { + fn global_max() -> u64 { + std::u64::MAX + } + fn global_min() -> u64 { + std::u64::MIN + } + + fn incr(&self) -> u64 { + self + 1 + } + fn decr(&self) -> u64 { + self - 1 + } +} + +/// A `Range` is a range of values of type T. Given this range: +/// +/// ```ignore +/// Range { +/// first: 0, +/// last: 3 +/// } +/// ``` +/// +/// This represents the inclusive range `[0, 3]`. (Where '[' and ']' represent +/// inclusive contains.) More specifically: it is equivalent to `0, 1, 2, 3`. +/// +/// --- +/// +/// `Range`s are only useful in cases in which `T` is an integer. AKA when +/// `T` has discrete values. Because Sway does not have floats, this means that +/// `Range` can be used for all numeric and integer Sway types. +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct Range +where + T: Debug + + fmt::Display + + Eq + + Ord + + PartialEq + + PartialOrd + + Clone + + MyMath + + Sub + + Into, +{ + first: T, + last: T, +} + +impl Range { + pub(crate) fn u8() -> Range { + Range { + first: std::u8::MIN, + last: std::u8::MAX, + } + } +} + +impl Range { + pub(crate) fn u16() -> Range { + Range { + first: std::u16::MIN, + last: std::u16::MAX, + } + } +} + +impl Range { + pub(crate) fn u32() -> Range { + Range { + first: std::u32::MIN, + last: std::u32::MAX, + } + } +} + +impl Range { + pub(crate) fn u64() -> Range { + Range { + first: std::u64::MIN, + last: std::u64::MAX, + } + } +} + +impl Range +where + T: Debug + + fmt::Display + + Eq + + Ord + + PartialEq + + PartialOrd + + Clone + + MyMath + + Sub + + Into, +{ + /// Creates a `Range` from a single value of type `T`, where the value is used + /// both as the lower inclusive contains and the upper inclusive contains. + pub(crate) fn from_single(x: T) -> Range { + Range { + first: x.clone(), + last: x, + } + } + + /// Creates a `Range` and ensures that it is a "valid `Range`" + /// (i.e.) that `first` is <= to `last` + fn from_double(first: T, last: T, span: &Span) -> CompileResult> { + let warnings = vec![]; + let mut errors = vec![]; + if last < first { + errors.push(CompileError::Internal( + "attempted to create an invalid range", + span.clone(), + )); + err(warnings, errors) + } else { + ok(Range { first, last }, warnings, errors) + } + } + + /// Combines two ranges that overlap. There are 6 ways + /// in which this might be the case: + /// + /// ```ignore + /// A: |------------| + /// B: |------| + /// -> |------------| + /// + /// A: |------| + /// B: |------------| + /// -> |------------| + /// + /// A: |---------| + /// B: |---------| + /// -> |--------------| + /// + /// A: |---------| + /// B: |---------| + /// -> |--------------| + /// + /// A: |------| + /// B: |------| + /// -> |--------------| + /// + /// A: |------| + /// B: |------| + /// -> |--------------| + /// ``` + /// + /// --- + /// + /// Note that becaues `Range` relies on the assumption that `T` is an + /// integer value, this algorithm joins `Range`s that are within ± 1 of + /// one another. Given these two `Range`s: + /// + /// ```ignore + /// Range { + /// first: 0, + /// last: 3 + /// } + /// Range { + /// first: 4, + /// last: 7 + /// } + /// ``` + /// + /// They can be joined into this `Range`: + /// + /// ```ignore + /// Range { + /// first: 0, + /// last: 7 + /// } + /// ``` + fn join_ranges(a: &Range, b: &Range, span: &Span) -> CompileResult> { + let mut warnings = vec![]; + let mut errors = vec![]; + if !a.overlaps(b) && !a.within_one(b) { + errors.push(CompileError::Internal( + "these two ranges cannot be joined", + span.clone(), + )); + err(warnings, errors) + } else { + let first = if a.first < b.first { + a.first.clone() + } else { + b.first.clone() + }; + let last = if a.last > b.last { + a.last.clone() + } else { + b.last.clone() + }; + let range = check!( + Range::from_double(first, last, span), + return err(warnings, errors), + warnings, + errors + ); + ok(range, warnings, errors) + } + } + + /// Condenses a `Vec>` to a `Vec>` of ordered, distinct, + /// non-overlapping ranges. + /// + /// Modeled after the algorithm here: https://www.geeksforgeeks.org/merging-intervals/ + /// + /// 1. Sort the intervals based on increasing order of starting time. + /// 2. Push the first interval on to a stack. + /// 3. For each interval do the following + /// 3a. If the current interval does not overlap with the stack + /// top, push it. + /// 3b. If the current interval overlaps with stack top (or is within ± 1) + /// and ending time of current interval is more than that of stack top, + /// update stack top with the ending time of current interval. + /// 4. At the end stack contains the merged intervals. + fn condense_ranges(ranges: Vec>, span: &Span) -> CompileResult>> { + let mut warnings = vec![]; + let mut errors = vec![]; + let mut ranges = ranges; + let mut stack: Vec> = vec![]; + + // 1. Sort the intervals based on increasing order of starting time. + ranges.sort_by(|a, b| b.first.cmp(&a.first)); + + // 2. Push the first interval on to a stack. + let (first, rest) = match ranges.split_first() { + Some((first, rest)) => (first.to_owned(), rest.to_owned()), + None => { + errors.push(CompileError::Internal("unable to split vec", span.clone())); + return err(warnings, errors); + } + }; + stack.push(first); + + for range in rest.iter() { + let top = match stack.pop() { + Some(top) => top, + None => { + errors.push(CompileError::Internal("stack empty", span.clone())); + return err(warnings, errors); + } + }; + if range.overlaps(&top) || range.within_one(&top) { + // 3b. If the current interval overlaps with stack top (or is within ± 1) + // and ending time of current interval is more than that of stack top, + // update stack top with the ending time of current interval. + stack.push(check!( + Range::join_ranges(range, &top, span), + return err(warnings, errors), + warnings, + errors + )); + } else { + // 3a. If the current interval does not overlap with the stack + // top, push it. + stack.push(top); + stack.push(range.clone()); + } + } + stack.reverse(); + ok(stack, warnings, errors) + } + + /// Given an *oracle* `Range` and a vec *guides* of `Range`, this + /// function returns the subdivided `Range`s that are both within + /// *oracle* not within *guides*. + /// + /// The steps are as follows: + /// + /// 1. Convert *guides* to a vec of ordered, distinct, non-overlapping + /// ranges *guides*' + /// 2. Check to ensure that *oracle* fully encompasses all ranges in + /// *guides*'. For example, this would pass the check: + /// ```ignore + /// oracle: |--------------| + /// guides: |--| |--| + /// ``` + /// But this would not: + /// ```ignore + /// oracle: |--------------| + /// guides: |--| |--| |---| + /// ``` + /// 3. Given the *oracle* range `[a, b]` and the *guides*'₀ range of + /// `[c, d]`, and `a != c`, construct a range of `[a, c]`. + /// 4. Given *guides*' of length *n*, for every *k* 0..*n-1*, find the + /// *guides*'ₖ range of `[a,b]` and the *guides*'ₖ₊₁ range of `[c, d]`, + /// construct a range of `[b, c]`. You can assume that `b != d` because + /// of step (1) + /// 5. Given the *oracle* range of `[a, b]`, *guides*' of length *n*, and + /// the *guides*'ₙ range of `[c, d]`, and `b != d`, construct a range of + /// `[b, d]`. + /// 6. Combine the range given from step (3), the ranges given from step + /// (4), and the range given from step (5) for your result. + pub(crate) fn find_exclusionary_ranges( + guides: Vec>, + oracle: Range, + span: &Span, + ) -> CompileResult>> { + let mut warnings = vec![]; + let mut errors = vec![]; + + // 1. Convert *guides* to a vec of ordered, distinct, non-overlapping + // ranges *guides*' + let condensed = check!( + Range::condense_ranges(guides, span), + return err(warnings, errors), + warnings, + errors + ); + + // 2. Check to ensure that *oracle* fully encompasses all ranges in + // *guides*'. + if !oracle.encompasses_all(&condensed) { + errors.push(CompileError::Internal( + "ranges OOB with the oracle", + span.clone(), + )); + return err(warnings, errors); + } + + // 3. Given the *oracle* range `[a, b]` and the *guides*'₀ range of + // `[c, d]`, and `a != c`, construct a range of `[a, c]`. + let mut exclusionary = vec![]; + let (first, last) = match (condensed.split_first(), condensed.split_last()) { + (Some((first, _)), Some((last, _))) => (first, last), + _ => { + errors.push(CompileError::Internal("could not split vec", span.clone())); + return err(warnings, errors); + } + }; + if oracle.first != first.first { + exclusionary.push(check!( + Range::from_double(oracle.first.clone(), first.first.decr(), span), + return err(warnings, errors), + warnings, + errors + )); + } + + // 4. Given *guides*' of length *n*, for every *k* 0..*n-1*, find the + // *guides*'ₖ range of `[a,b]` and the *guides*'ₖ₊₁ range of `[c, d]`, + // construct a range of `[b, c]`. You can assume that `b != d` because + // of step (1) + for (left, right) in condensed.iter().tuple_windows() { + exclusionary.push(check!( + Range::from_double(left.last.incr(), right.first.decr(), span), + return err(warnings, errors), + warnings, + errors + )); + } + + // 5. Given the *oracle* range of `[a, b]`, *guides*' of length *n*, and + // the *guides*'ₙ range of `[c, d]`, and `b != d`, construct a range of + // `[b, d]`. + if oracle.last != last.last { + exclusionary.push(check!( + Range::from_double(last.last.incr(), oracle.last, span), + return err(warnings, errors), + warnings, + errors + )); + } + + // 6. Combine the range given from step (3), the ranges given from step + // (4), and the range given from step (5) for your result. + ok(exclusionary, warnings, errors) + } + + /// Condenses a vec of ranges and checks to see if the condensed ranges + /// equal an oracle range. + pub(crate) fn do_ranges_equal_range( + ranges: Vec>, + oracle: Range, + span: &Span, + ) -> CompileResult { + let mut warnings = vec![]; + let mut errors = vec![]; + let condensed_ranges = check!( + Range::condense_ranges(ranges, span), + return err(warnings, errors), + warnings, + errors + ); + if condensed_ranges.len() > 1 { + ok(false, warnings, errors) + } else { + let first_range = match condensed_ranges.first() { + Some(first_range) => first_range.clone(), + _ => { + errors.push(CompileError::Internal("vec empty", span.clone())); + return err(warnings, errors); + } + }; + ok(first_range == oracle, warnings, errors) + } + } + + /// Checks to see if two ranges overlap. There are 4 ways in which this + /// might be the case: + /// + /// ```ignore + /// A: |------------| + /// B: |------| + /// + /// A: |------| + /// B: |------------| + /// + /// A: |---------| + /// B: |---------| + /// + /// A: |---------| + /// B: |---------| + /// ``` + fn overlaps(&self, other: &Range) -> bool { + other.first >= self.first && other.last <= self.last + || other.first <= self.first && other.last >= self.last + || other.first <= self.first && other.last <= self.last && other.last >= self.first + || other.first >= self.first && other.first <= self.last && other.last >= self.last + } + + /// Checks to see if the first range encompasses the second range. There are + /// 2 ways in which this might be the case: + /// + /// ```ignore + /// A: |------------| + /// B: |------| + /// + /// A: |------------| + /// B: |------------| + /// ``` + fn encompasses(&self, other: &Range) -> bool { + self.first <= other.first && self.last >= other.last + } + + fn encompasses_all(&self, others: &[Range]) -> bool { + others + .iter() + .map(|other| self.encompasses(other)) + .all(|x| x) + } + + /// Checks to see if two ranges are within ± 1 of one another. There are 2 + /// ways in which this might be the case: + /// + /// ```ignore + /// A: |------| + /// B: |------| + /// + /// A: |------| + /// B: |------| + /// ``` + fn within_one(&self, other: &Range) -> bool { + !self.overlaps(other) + && (other.first > self.last && (other.first.clone() - self.last.clone()).into() == 1u64 + || self.first > other.last + && (self.first.clone() - other.last.clone()).into() == 1u64) + } +} + +impl fmt::Display for Range +where + T: Debug + + fmt::Display + + Eq + + Ord + + PartialEq + + PartialOrd + + Clone + + MyMath + + Sub + + Into, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut builder = String::new(); + builder.push('['); + if self.first == T::global_min() { + builder.push_str("MIN"); + } else { + builder.push_str(&format!("{}", self.first)); + } + builder.push_str("..."); + if self.last == T::global_max() { + builder.push_str("MAX"); + } else { + builder.push_str(&format!("{}", self.last)); + } + builder.push(']'); + write!(f, "{}", builder) + } +} diff --git a/sway-core/src/semantic_analysis/ast_node/expression/usefulness/witness_report.rs b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/witness_report.rs new file mode 100644 index 00000000000..372e2364a7e --- /dev/null +++ b/sway-core/src/semantic_analysis/ast_node/expression/usefulness/witness_report.rs @@ -0,0 +1,119 @@ +use std::fmt; + +use itertools::Itertools; +use sway_types::Span; + +use crate::{ + error::{err, ok}, + CompileError, CompileResult, +}; + +use super::{patstack::PatStack, pattern::Pattern}; + +/// A `WitnessReport` is a report of the witnesses to a `Pattern` being useful +/// and is used in the match expression exhaustivity checking algorithm. +#[derive(Debug)] +pub(crate) enum WitnessReport { + NoWitnesses, + Witnesses(PatStack), +} + +impl WitnessReport { + /// Joins two `WitnessReport`s together. + pub(crate) fn join_witness_reports(a: WitnessReport, b: WitnessReport) -> Self { + match (a, b) { + (WitnessReport::NoWitnesses, WitnessReport::NoWitnesses) => WitnessReport::NoWitnesses, + (WitnessReport::NoWitnesses, WitnessReport::Witnesses(wits)) => { + WitnessReport::Witnesses(wits) + } + (WitnessReport::Witnesses(wits), WitnessReport::NoWitnesses) => { + WitnessReport::Witnesses(wits) + } + (WitnessReport::Witnesses(wits1), WitnessReport::Witnesses(mut wits2)) => { + let mut wits = wits1; + wits.append(&mut wits2); + WitnessReport::Witnesses(wits) + } + } + } + + /// Given a `WitnessReport` *wr* and a constructor *c* with *a* number of + /// sub-patterns, creates a new `Pattern` *p* and a new `WitnessReport` + /// *wr'*. *p* is created by applying *c* to the first *a* elements of *wr*. + /// *wr'* is created by taking the remaining elements of *wr* after *a* + /// elements have been removed from the front of *wr*. + pub(crate) fn split_into_leading_constructor( + witness_report: WitnessReport, + c: &Pattern, + span: &Span, + ) -> CompileResult<(Pattern, Self)> { + let mut warnings = vec![]; + let mut errors = vec![]; + match witness_report { + WitnessReport::NoWitnesses => { + errors.push(CompileError::Internal( + "expected to find witnesses to use as arguments to a constructor", + span.clone(), + )); + err(warnings, errors) + } + WitnessReport::Witnesses(witnesses) => { + let (rs, ps) = check!( + witnesses.split_at(c.a(), span), + return err(warnings, errors), + warnings, + errors + ); + let pat = check!( + Pattern::from_constructor_and_arguments(c, rs, span), + return err(warnings, errors), + warnings, + errors + ); + ok((pat, WitnessReport::Witnesses(ps)), warnings, errors) + } + } + } + + /// Prepends a witness `Pattern` onto the `WitnessReport`. + pub(crate) fn add_witness(&mut self, witness: Pattern, span: &Span) -> CompileResult<()> { + let warnings = vec![]; + let mut errors = vec![]; + match self { + WitnessReport::NoWitnesses => { + errors.push(CompileError::Internal( + "expected to find witnesses", + span.clone(), + )); + err(warnings, errors) + } + WitnessReport::Witnesses(witnesses) => { + witnesses.prepend(witness); + ok((), warnings, errors) + } + } + } + + /// Reports if this `WitnessReport` has witnesses. + pub(crate) fn has_witnesses(&self) -> bool { + match self { + WitnessReport::NoWitnesses => false, + WitnessReport::Witnesses(_) => true, // !witnesses.is_empty() + } + } +} + +impl fmt::Display for WitnessReport { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let witnesses = match self { + WitnessReport::NoWitnesses => PatStack::empty(), + WitnessReport::Witnesses(witnesses) => witnesses.clone(), + }; + let s = witnesses + .flatten() + .into_iter() + .map(|x| format!("`{}`", x)) + .join(", "); + write!(f, "{}", s) + } +} diff --git a/sway-core/src/semantic_analysis/node_dependencies.rs b/sway-core/src/semantic_analysis/node_dependencies.rs index eecad3c6b9f..0370f584235 100644 --- a/sway-core/src/semantic_analysis/node_dependencies.rs +++ b/sway-core/src/semantic_analysis/node_dependencies.rs @@ -2,8 +2,8 @@ use std::collections::{HashMap, HashSet}; use std::iter::FromIterator; use crate::{ - error::*, parse_tree::Scrutinee, parse_tree::*, type_engine::IntegerBits, AstNode, - AstNodeContent, CodeBlock, Declaration, Expression, ReturnStatement, TypeInfo, WhileLoop, + error::*, parse_tree::*, type_engine::IntegerBits, AstNode, AstNodeContent, CodeBlock, + Declaration, Expression, ReturnStatement, TypeInfo, WhileLoop, }; use sway_types::{ident::Ident, span::Span}; @@ -361,6 +361,7 @@ impl Dependencies { } .gather_from_expr(condition) .gather_from_expr(then), + Expression::MatchExp { if_exp, .. } => self.gather_from_expr(if_exp), Expression::CodeBlock { contents, .. } => self.gather_from_block(contents), Expression::Array { contents, .. } => { self.gather_from_iter(contents.iter(), |deps, expr| deps.gather_from_expr(expr)) @@ -394,22 +395,6 @@ impl Dependencies { deps.gather_from_opt_expr(®ister.initializer) }) .gather_from_typeinfo(&asm.return_type), - Expression::MatchExpression { - primary_expression, - branches, - .. - } => self.gather_from_expr(primary_expression).gather_from_iter( - branches.iter(), - |deps, branch| { - match &branch.condition { - MatchCondition::CatchAll(_) => deps, - MatchCondition::Scrutinee(scrutinee) => { - deps.gather_from_scrutinee(scrutinee) - } - } - .gather_from_expr(&branch.result) - }, - ), // Not sure about AbiCast, could add the abi_name and address. Expression::AbiCast { .. } => self, @@ -420,18 +405,12 @@ impl Dependencies { } Expression::TupleIndex { prefix, .. } => self.gather_from_expr(prefix), Expression::DelayedMatchTypeResolution { .. } => self, - Expression::IfLet { - expr, scrutinee, .. - } => self.gather_from_expr(expr).gather_from_scrutinee(scrutinee), + Expression::IfLet { expr, .. } => self.gather_from_expr(expr), Expression::SizeOfVal { exp, .. } => self.gather_from_expr(exp), Expression::SizeOfType { .. } => self, } } - fn gather_from_scrutinee(self, _scrutinee: &Scrutinee) -> Self { - self - } - fn gather_from_opt_expr(self, opt_expr: &Option) -> Self { match opt_expr { None => self, diff --git a/test/src/e2e_vm_tests/mod.rs b/test/src/e2e_vm_tests/mod.rs index 1dbdaf8c927..bbf28653ad7 100644 --- a/test/src/e2e_vm_tests/mod.rs +++ b/test/src/e2e_vm_tests/mod.rs @@ -323,6 +323,7 @@ pub fn run(filter_regex: Option) { "should_fail/supertrait_does_not_exist", "should_fail/chained_if_let_missing_branch", "should_fail/abort_control_flow", + "should_fail/match_expressions_non_exhaustive", ]; number_of_tests_run += negative_project_names.iter().fold(0, |acc, name| { if filter(name) { diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/abort_control_flow/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/abort_control_flow/Forc.lock index cf7542a4df9..3d0ecd9827c 100644 --- a/test/src/e2e_vm_tests/test_programs/should_fail/abort_control_flow/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_fail/abort_control_flow/Forc.lock @@ -1,6 +1,6 @@ [[package]] name = 'abort_control_flow' -dependencies = ['std git+http://github.com/FuelLabs/sway-lib-std?reference=master#55a820c8f30c87b4094bee5b8060765aa71bd900'] +dependencies = ['std git+https://github.com/FuelLabs/sway-lib-std?reference=master#aa36aea9362575c769781e7ab640d1d75dce13c8'] [[package]] name = 'core' @@ -9,5 +9,5 @@ dependencies = [] [[package]] name = 'std' -source = 'git+http://github.com/FuelLabs/sway-lib-std?reference=master#55a820c8f30c87b4094bee5b8060765aa71bd900' +source = 'git+https://github.com/FuelLabs/sway-lib-std?reference=master#aa36aea9362575c769781e7ab640d1d75dce13c8' dependencies = ['core git+https://github.com/FuelLabs/sway-lib-core?reference=master#30274cf817c1848e28f984c2e8703eb25e7a3a44'] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/Forc.lock new file mode 100644 index 00000000000..828e67200ab --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/Forc.lock @@ -0,0 +1,16 @@ +[[package]] +name = 'core' +source = 'git+http://github.com/FuelLabs/sway-lib-core?reference=master#c331ed20ebc9d646acec6b8ee8f408627ce3b573' +dependencies = [] + +[[package]] +name = 'match_expressions_non_exhaustive' +dependencies = [ + 'core git+http://github.com/FuelLabs/sway-lib-core?reference=master#c331ed20ebc9d646acec6b8ee8f408627ce3b573', + 'std git+http://github.com/FuelLabs/sway-lib-std?reference=master#7081d2e1ac2401f22a0de0673e8a01535180ba3a', +] + +[[package]] +name = 'std' +source = 'git+http://github.com/FuelLabs/sway-lib-std?reference=master#7081d2e1ac2401f22a0de0673e8a01535180ba3a' +dependencies = ['core git+http://github.com/FuelLabs/sway-lib-core?reference=master#c331ed20ebc9d646acec6b8ee8f408627ce3b573'] diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/Forc.toml new file mode 100644 index 00000000000..d9d339f33f3 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/Forc.toml @@ -0,0 +1,9 @@ +[project] +author = "Fuel Labs " +license = "Apache-2.0" +name = "match_expressions_non_exhaustive" +entry = "main.sw" + +[dependencies] +std = { git = "http://github.com/FuelLabs/sway-lib-std", branch = "master" } +core = { git = "http://github.com/FuelLabs/sway-lib-core", branch = "master" } diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/json_abi_oracle.json new file mode 100644 index 00000000000..0637a088a01 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/json_abi_oracle.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/src/main.sw new file mode 100644 index 00000000000..bb4a080981d --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_fail/match_expressions_non_exhaustive/src/main.sw @@ -0,0 +1,152 @@ +script; + +struct Point { + x: u64, + y: u64 +} + +struct CrazyPoint { + p1: Point, + p2: Point +} + +fn a(x: u64) -> u64 { + match x { + 7 => { 0 }, + _ => { 1 }, + } +} + +fn b(x: u64) -> u64 { + match x { + 14 => { 7 }, + _ => { 1 }, + } +} + +fn c(x: u64) -> u64 { + match x { + 21 => { 7 }, + _ => { 1 }, + } +} + +fn main() -> u64 { + let x = 0; + // should fail + let y = match x { + 0 => { 0 }, + 10 => { 0 }, + 5 => { 0 }, + 10 => { 0 }, + }; + // should succeed + let y = match x { + 0 => { 0 }, + 1 => { 0 }, + _ => { 0 }, + }; + // should succeed + let y = match x { + 0 => { 0 }, + 1 => { 0 }, + a => { a }, + }; + + let x = (1, 2); + // should fail + let y = match x { + (0, 0) => { 0 }, + (1, 1) => { 0 }, + (1, 1) => { 0 }, + (1, 2) => { 0 }, + }; + // should succeed + let y = match x { + (0, 0) => { 0 }, + (1, 1) => { 0 }, + _ => { 0 }, + }; + // should succeed + let y = match x { + (0, 0) => { 0 }, + (1, 1) => { 0 }, + a => { 0 }, + }; + // should succeed + let y = match x { + (0, 0) => { 0 }, + (1, 1) => { 0 }, + (a, b) => { 0 }, + }; + + let p = Point { + x: 3, + y: 4, + }; + // should fail + let foo = match p { + Point { x: 3, y } => { y }, + Point { x: 3, y: 4 } => { 24 }, + }; + // should succeed + let foo = match p { + Point { x: 3, y } => { y }, + Point { x: 3, y: 4 } => { 24 }, + Point { x, y } => { x }, + }; + // should succeed + let foo = match p { + Point { x: 3, y } => { y }, + Point { x: 3, y: 4 } => { 24 }, + a => { 24 }, + }; + // should succeed + let foo = match p { + Point { x: 3, y } => { y }, + Point { x: 3, y: 4 } => { 24 }, + _ => { 24 }, + }; + + let p = CrazyPoint { + p1: Point { + x: 100, + y: 200 + }, + p2: Point { + x: 300, + y: 400 + } + }; + // should fail + let foo = match p { + CrazyPoint { p1: Point { x: 0, y: 1 }, p2 } => { 42 }, + }; + + // should fail + let foo = match 42 { + 0 => { newvariable}, + foo => { foo }, + }; + + // should succeed + let foo = match (match 1 { + 1 => { 1 }, + _ => { 0 }, + }) { + 0 => { 42 }, + _ => { 0 }, + }; + + let q = 21; + let foo = match a(match q { + 14 => { b(q) }, + 21 => { c(q) }, + _ => { q }, + }) { + 0 => { 42 }, + _ => { 24 }, + }; + + 42u64 +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/abort_control_flow/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/abort_control_flow/Forc.lock index cf7542a4df9..3d0ecd9827c 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/abort_control_flow/Forc.lock +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/abort_control_flow/Forc.lock @@ -1,6 +1,6 @@ [[package]] name = 'abort_control_flow' -dependencies = ['std git+http://github.com/FuelLabs/sway-lib-std?reference=master#55a820c8f30c87b4094bee5b8060765aa71bd900'] +dependencies = ['std git+https://github.com/FuelLabs/sway-lib-std?reference=master#aa36aea9362575c769781e7ab640d1d75dce13c8'] [[package]] name = 'core' @@ -9,5 +9,5 @@ dependencies = [] [[package]] name = 'std' -source = 'git+http://github.com/FuelLabs/sway-lib-std?reference=master#55a820c8f30c87b4094bee5b8060765aa71bd900' +source = 'git+https://github.com/FuelLabs/sway-lib-std?reference=master#aa36aea9362575c769781e7ab640d1d75dce13c8' dependencies = ['core git+https://github.com/FuelLabs/sway-lib-core?reference=master#30274cf817c1848e28f984c2e8703eb25e7a3a44']