use crate::env::Env; use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. #[derive(Clone, Debug, PartialEq)] pub enum Pattern { Identifier(Symbol), AppliedTag { whole_var: Variable, ext_var: Variable, tag_name: TagName, arguments: Vec<(Variable, Located)>, }, RecordDestructure { whole_var: Variable, ext_var: Variable, destructs: Vec>, }, IntLiteral(i64), NumLiteral(Variable, i64), FloatLiteral(f64), StrLiteral(Box), Underscore, // Runtime Exceptions Shadowed(Region, Located), // Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments! UnsupportedPattern(Region), // parse error patterns MalformedPattern(MalformedPatternProblem, Region), } #[derive(Clone, Debug, PartialEq)] pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, pub symbol: Symbol, pub guard: Option<(Variable, Located)>, } pub fn symbols_from_pattern(pattern: &Pattern) -> Vec { let mut symbols = Vec::new(); symbols_from_pattern_help(pattern, &mut symbols); symbols } pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { use Pattern::*; match pattern { Identifier(symbol) => { symbols.push(*symbol); } AppliedTag { arguments, .. } => { for (_, nested) in arguments { symbols_from_pattern_help(&nested.value, symbols); } } RecordDestructure { destructs, .. } => { for destruct in destructs { symbols.push(destruct.value.symbol); } } NumLiteral(_, _) | IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) => {} Shadowed(_, _) => {} } } pub fn canonicalize_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, ) -> Located { use roc_parse::ast::Pattern::*; use PatternType::*; let can_pattern = match pattern { Identifier(name) => match scope.introduce( (*name).into(), &env.exposed_ident_ids, &mut env.ident_ids, region, ) { Ok(symbol) => Pattern::Identifier(symbol), Err((original_region, shadow)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), })); Pattern::Shadowed(original_region, shadow) } }, GlobalTag(name) => { // Canonicalize the tag's name. Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), tag_name: TagName::Global((*name).into()), arguments: vec![], } } PrivateTag(name) => { let ident_id = env.ident_ids.get_or_insert(&(*name).into()); // Canonicalize the tag's name. Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), tag_name: TagName::Private(Symbol::new(env.home, ident_id)), arguments: vec![], } } Apply(tag, patterns) => { let tag_name = match tag.value { GlobalTag(name) => TagName::Global(name.into()), PrivateTag(name) => { let ident_id = env.ident_ids.get_or_insert(&name.into()); TagName::Private(Symbol::new(env.home, ident_id)) } _ => unreachable!("Other patterns cannot be applied"), }; let mut can_patterns = Vec::with_capacity(patterns.len()); for loc_pattern in *patterns { can_patterns.push(( var_store.fresh(), canonicalize_pattern( env, var_store, scope, pattern_type, &loc_pattern.value, loc_pattern.region, ), )); } Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), tag_name, arguments: can_patterns, } } FloatLiteral(ref string) => match pattern_type { WhenBranch => match finish_parsing_float(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } Ok(float) => Pattern::FloatLiteral(float), }, ptype => unsupported_pattern(env, ptype, region), }, Underscore => match pattern_type { WhenBranch | FunctionArg => Pattern::Underscore, ptype => unsupported_pattern(env, ptype, region), }, NumLiteral(string) => match pattern_type { WhenBranch => match finish_parsing_int(string) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } Ok(int) => Pattern::NumLiteral(var_store.fresh(), int), }, ptype => unsupported_pattern(env, ptype, region), }, NonBase10Literal { string, base, is_negative, } => match pattern_type { WhenBranch => match finish_parsing_base(string, *base, *is_negative) { Err(_error) => { let problem = MalformedPatternProblem::MalformedBase(*base); malformed_pattern(env, problem, region) } Ok(int) => { if *is_negative { Pattern::IntLiteral(-int) } else { Pattern::IntLiteral(int) } } }, ptype => unsupported_pattern(env, ptype, region), }, StrLiteral(string) => match pattern_type { WhenBranch => { // TODO report whether string was malformed Pattern::StrLiteral((*string).into()) } ptype => unsupported_pattern(env, ptype, region), }, BlockStrLiteral(_lines) => match pattern_type { WhenBranch => todo!("TODO block string literal pattern"), ptype => unsupported_pattern(env, ptype, region), }, SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => { return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region) } RecordDestructure(patterns) => { let ext_var = var_store.fresh(); let whole_var = var_store.fresh(); let mut destructs = Vec::with_capacity(patterns.len()); let mut opt_erroneous = None; for loc_pattern in *patterns { match loc_pattern.value { Identifier(label) => { match scope.introduce( label.into(), &env.exposed_ident_ids, &mut env.ident_ids, region, ) { Ok(symbol) => { destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), label: Lowercase::from(label), symbol, guard: None, }, }); } Err((original_region, shadow)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), })); // No matter what the other patterns // are, we're definitely shadowed and will // get a runtime exception as soon as we // encounter the first bad pattern. opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); } }; } RecordField(label, loc_guard) => { // a guard does not introduce the label into scope! let symbol = scope.ignore(label.into(), &mut env.ident_ids); let can_guard = canonicalize_pattern( env, var_store, scope, pattern_type, &loc_guard.value, loc_guard.region, ); destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), label: Lowercase::from(label), symbol, guard: Some((var_store.fresh(), can_guard)), }, }); } _ => unreachable!("Any other pattern should have given a parse error"), } } // If we encountered an erroneous pattern (e.g. one with shadowing), // use the resulting RuntimeError. Otherwise, return a successful record destructure. opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure { whole_var, ext_var, destructs, }) } RecordField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); } Malformed(_str) => { let problem = MalformedPatternProblem::Unknown; malformed_pattern(env, problem, region) } QualifiedIdentifier { .. } => { let problem = MalformedPatternProblem::QualifiedIdentifier; malformed_pattern(env, problem, region) } }; Located { region, value: can_pattern, } } /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't /// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern. fn unsupported_pattern<'a>( env: &mut Env<'a>, pattern_type: PatternType, region: Region, ) -> Pattern { env.problem(Problem::UnsupportedPattern(pattern_type, region)); Pattern::UnsupportedPattern(region) } /// When we detect a malformed pattern like `3.X` or `0b5`, /// report it to Env and return an UnsupportedPattern runtime error pattern. fn malformed_pattern<'a>( env: &mut Env<'a>, problem: MalformedPatternProblem, region: Region, ) -> Pattern { env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( problem, region, ))); Pattern::MalformedPattern(problem, region) } pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)> where I: Iterator>, { let mut answer = Vec::new(); for loc_pattern in loc_patterns { add_bindings_from_patterns(&loc_pattern.region, &loc_pattern.value, scope, &mut answer); } answer } /// helper function for idents_from_patterns fn add_bindings_from_patterns( region: &Region, pattern: &Pattern, scope: &Scope, answer: &mut Vec<(Symbol, Region)>, ) { use Pattern::*; match pattern { Identifier(symbol) => { answer.push((*symbol, *region)); } AppliedTag { arguments: loc_args, .. } => { for (_, loc_arg) in loc_args { add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, scope, answer); } } RecordDestructure { destructs, .. } => { for Located { region, value: RecordDestruct { symbol, .. }, } in destructs { answer.push((*symbol, *region)); } } NumLiteral(_, _) | IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => (), } }