use crate::annotation::freshen_opaque_def; use crate::env::Env; use crate::expr::{canonicalize_expr, Expr, IntValue, Output}; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, NumBound, ParsedNumResult, }; use crate::scope::{PendingAbilitiesInScope, Scope}; use roc_exhaustive::ListArity; use roc_module::ident::{Ident, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_parse::ast::{self, StrLiteral, StrSegment}; use roc_parse::pattern::PatternType; use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; use roc_types::num::SingleQuoteBound; use roc_types::subs::{VarStore, Variable}; use roc_types::types::{LambdaSet, OptAbleVar, PatternCategory, Type}; /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. #[derive(Clone, Debug)] pub enum Pattern { Identifier(Symbol), As(Box>, Symbol), AppliedTag { whole_var: Variable, ext_var: Variable, tag_name: TagName, arguments: Vec<(Variable, Loc)>, }, UnwrappedOpaque { whole_var: Variable, opaque: Symbol, argument: Box<(Variable, Loc)>, // The following help us link this opaque reference to the type specified by its // definition, which we then use during constraint generation. For example // suppose we have // // Id n := [Id U64 n] // strToBool : Str -> Bool // // f = \@Id who -> strToBool who // // Then `opaque` is "Id", `argument` is "who", but this is not enough for us to // infer the type of the expression as "Id Str" - we need to link the specialized type of // the variable "n". // That's what `specialized_def_type` and `type_arguments` are for; they are specialized // for the expression from the opaque definition. `type_arguments` is something like // [(n, fresh1)], and `specialized_def_type` becomes "[Id U64 fresh1]". specialized_def_type: Box, type_arguments: Vec, lambda_set_variables: Vec, }, RecordDestructure { whole_var: Variable, ext_var: Variable, destructs: Vec>, }, TupleDestructure { whole_var: Variable, ext_var: Variable, destructs: Vec>, }, List { list_var: Variable, elem_var: Variable, patterns: ListPatterns, }, NumLiteral(Variable, Box, IntValue, NumBound), IntLiteral(Variable, Variable, Box, IntValue, IntBound), FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), SingleQuote(Variable, Variable, char, SingleQuoteBound), Underscore, /// An identifier that marks a specialization of an ability member. /// For example, given an ability member definition `hash : a -> U64 where a implements Hash`, /// there may be the specialization `hash : Bool -> U64`. In this case we generate a /// new symbol for the specialized "hash" identifier. AbilityMemberSpecialization { /// The symbol for this specialization. ident: Symbol, /// The ability name being specialized. specializes: Symbol, }, // Runtime Exceptions Shadowed(Region, Loc, Symbol), OpaqueNotInScope(Loc), // 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), } impl Pattern { pub fn opt_var(&self) -> Option { use Pattern::*; match self { Identifier(_) => None, As(pattern, _) => pattern.value.opt_var(), AppliedTag { whole_var, .. } => Some(*whole_var), UnwrappedOpaque { whole_var, .. } => Some(*whole_var), RecordDestructure { whole_var, .. } => Some(*whole_var), TupleDestructure { whole_var, .. } => Some(*whole_var), List { list_var: whole_var, .. } => Some(*whole_var), NumLiteral(var, ..) => Some(*var), IntLiteral(var, ..) => Some(*var), FloatLiteral(var, ..) => Some(*var), StrLiteral(_) => None, SingleQuote(..) => None, Underscore => None, AbilityMemberSpecialization { .. } => None, Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) => { None } } } /// Is this pattern sure to cover all instances of a type T, assuming it typechecks against T? pub fn surely_exhaustive(&self) -> bool { use Pattern::*; match self { Identifier(..) | Underscore | Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) | AbilityMemberSpecialization { .. } => true, RecordDestructure { destructs, .. } => { // If all destructs are surely exhaustive, then this is surely exhaustive. destructs.iter().all(|d| match &d.value.typ { DestructType::Required | DestructType::Optional(_, _) => true, DestructType::Guard(_, pat) => pat.value.surely_exhaustive(), }) } TupleDestructure { destructs, .. } => { // If all destructs are surely exhaustive, then this is surely exhaustive. destructs .iter() .all(|d| d.value.typ.1.value.surely_exhaustive()) } As(pattern, _identifier) => pattern.value.surely_exhaustive(), List { patterns, .. } => patterns.surely_exhaustive(), AppliedTag { .. } | NumLiteral(..) | IntLiteral(..) | FloatLiteral(..) | StrLiteral(..) | SingleQuote(..) => false, UnwrappedOpaque { argument, .. } => { // Opaques can only match against one constructor (the opaque symbol), so this is // surely exhaustive against T if the inner pattern is surely exhaustive against // its type U. argument.1.value.surely_exhaustive() } } } pub fn category(&self) -> PatternCategory { use Pattern::*; use PatternCategory as C; match self { Identifier(_) => C::PatternDefault, As(pattern, _) => pattern.value.category(), AppliedTag { tag_name, .. } => C::Ctor(tag_name.clone()), UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque), RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord, RecordDestructure { .. } => C::Record, TupleDestructure { .. } => C::Tuple, List { .. } => C::List, NumLiteral(..) => C::Num, IntLiteral(..) => C::Int, FloatLiteral(..) => C::Float, StrLiteral(_) => C::Str, SingleQuote(..) => C::Character, Underscore => C::PatternDefault, AbilityMemberSpecialization { .. } => C::PatternDefault, Shadowed(..) | OpaqueNotInScope(..) | UnsupportedPattern(..) | MalformedPattern(..) => { C::PatternDefault } } } } #[derive(Clone, Debug)] pub struct ListPatterns { pub patterns: Vec>, /// Where a rest pattern splits patterns before and after it, if it does at all. /// If present, patterns at index >= the rest index appear after the rest pattern. /// For example: /// [ .., A, B ] -> patterns = [A, B], rest = 0 /// [ A, .., B ] -> patterns = [A, B], rest = 1 /// [ A, B, .. ] -> patterns = [A, B], rest = 2 pub opt_rest: Option<(usize, Option>)>, } impl ListPatterns { /// Is this list pattern the trivially-exhaustive pattern `[..]`? fn surely_exhaustive(&self) -> bool { self.patterns.is_empty() && matches!(self.opt_rest, Some((0, _))) } pub fn arity(&self) -> ListArity { match self.opt_rest { Some((i, _)) => { let before = i; let after = self.patterns.len() - before; ListArity::Slice(before, after) } None => ListArity::Exact(self.patterns.len()), } } } #[derive(Clone, Debug)] pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, pub symbol: Symbol, pub typ: DestructType, } #[derive(Clone, Debug)] pub struct TupleDestruct { pub var: Variable, pub destruct_index: usize, pub typ: (Variable, Loc), } #[derive(Clone, Debug)] pub enum DestructType { Required, Optional(Variable, Loc), Guard(Variable, Loc), } #[allow(clippy::too_many_arguments)] pub fn canonicalize_def_header_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, pending_abilities_in_scope: &PendingAbilitiesInScope, output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, ) -> Loc { use roc_parse::ast::Pattern::*; match pattern { // Identifiers that shadow ability members may appear (and may only appear) at the header of a def. Identifier { ident: name } => { match scope.introduce_or_shadow_ability_member( pending_abilities_in_scope, (*name).into(), region, ) { Ok((symbol, shadowing_ability_member)) => { let can_pattern = match shadowing_ability_member { // A fresh identifier. None => { output.references.insert_bound(symbol); Pattern::Identifier(symbol) } // Likely a specialization of an ability. Some(ability_member_name) => { output.references.insert_bound(symbol); Pattern::AbilityMemberSpecialization { ident: symbol, specializes: ability_member_name, } } }; Loc::at(region, can_pattern) } Err((original_region, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region, shadow: shadow.clone(), kind: ShadowKind::Variable, })); output.references.insert_bound(new_symbol); let can_pattern = Pattern::Shadowed(original_region, shadow, new_symbol); Loc::at(region, can_pattern) } } } _ => canonicalize_pattern( env, var_store, scope, output, pattern_type, pattern, region, PermitShadows(false), ), } } /// Allow binding of symbols that appear shadowed. /// /// For example, in the branch `A x | B x -> ...`, both pattern bind `x`; that's not a shadow! #[derive(PartialEq, Eq, Clone, Copy)] pub struct PermitShadows(pub bool); fn canonicalize_pattern_symbol( env: &mut Env, scope: &mut Scope, output: &mut Output, region: Region, permit_shadows: PermitShadows, name: &str, ) -> Result { match scope.introduce_str(name, region) { Ok(symbol) => { output.references.insert_bound(symbol); Ok(symbol) } Err((shadowed_symbol, shadow, new_symbol)) => { if permit_shadows.0 { output.references.insert_bound(shadowed_symbol.value); Ok(shadowed_symbol.value) } else { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region: shadowed_symbol.region, shadow: shadow.clone(), kind: ShadowKind::Variable, })); output.references.insert_bound(new_symbol); Err(Pattern::Shadowed( shadowed_symbol.region, shadow, new_symbol, )) } } } } #[allow(clippy::too_many_arguments)] pub fn canonicalize_pattern<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, output: &mut Output, pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, permit_shadows: PermitShadows, ) -> Loc { use roc_parse::ast::Pattern::*; use PatternType::*; let can_pattern = match pattern { Identifier { ident: name } => { match canonicalize_pattern_symbol(env, scope, output, region, permit_shadows, name) { Ok(symbol) => Pattern::Identifier(symbol), Err(pattern) => pattern, } } Underscore(name) => { // An underscored identifier can't be used, but we'll still add it to the scope // for better error messages if someone tries to use it. scope.introduce_ignored_local(name, region); Pattern::Underscore } Tag(name) => { // Canonicalize the tag's name. Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), tag_name: TagName((*name).into()), arguments: vec![], } } OpaqueRef(name) => { // If this opaque ref had an argument, we would be in the "Apply" branch. let loc_name = Loc::at(region, (*name).into()); env.problem(Problem::RuntimeError(RuntimeError::OpaqueNotApplied( loc_name, ))); Pattern::UnsupportedPattern(region) } Apply(tag, patterns) => { let mut can_patterns = Vec::with_capacity(patterns.len()); for loc_pattern in *patterns { let can_pattern = canonicalize_pattern( env, var_store, scope, output, pattern_type, &loc_pattern.value, loc_pattern.region, permit_shadows, ); can_patterns.push((var_store.fresh(), can_pattern)); } match tag.value { Tag(name) => { let tag_name = TagName(name.into()); Pattern::AppliedTag { whole_var: var_store.fresh(), ext_var: var_store.fresh(), tag_name, arguments: can_patterns, } } OpaqueRef(name) => match scope.lookup_opaque_ref(name, tag.region) { Ok((opaque, opaque_def)) => { debug_assert!(!can_patterns.is_empty()); if can_patterns.len() > 1 { env.problem(Problem::RuntimeError( RuntimeError::OpaqueAppliedToMultipleArgs(region), )); Pattern::UnsupportedPattern(region) } else { let argument = Box::new(can_patterns.pop().unwrap()); let (type_arguments, lambda_set_variables, specialized_def_type) = freshen_opaque_def(var_store, opaque_def); output.references.insert_type_lookup( opaque, crate::procedure::QualifiedReference::Unqualified, ); Pattern::UnwrappedOpaque { whole_var: var_store.fresh(), opaque, argument, specialized_def_type: Box::new(specialized_def_type), type_arguments, lambda_set_variables, } } } Err(runtime_error) => { env.problem(Problem::RuntimeError(runtime_error)); Pattern::OpaqueNotInScope(Loc::at(tag.region, name.into())) } }, _ => unreachable!("Other patterns cannot be applied"), } } &FloatLiteral(str) => match pattern_type { WhenBranch => match finish_parsing_float(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedFloat; malformed_pattern(env, problem, region) } Ok((str_without_suffix, float, bound)) => Pattern::FloatLiteral( var_store.fresh(), var_store.fresh(), str_without_suffix.into(), float, bound, ), }, ptype => unsupported_pattern(env, ptype, region), }, &NumLiteral(str) => match pattern_type { WhenBranch => match finish_parsing_num(str) { Err(_error) => { let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } Ok((parsed, ParsedNumResult::UnknownNum(int, bound))) => { Pattern::NumLiteral(var_store.fresh(), (parsed).into(), int, bound) } Ok((parsed, ParsedNumResult::Int(int, bound))) => Pattern::IntLiteral( var_store.fresh(), var_store.fresh(), (parsed).into(), int, bound, ), Ok((parsed, ParsedNumResult::Float(float, bound))) => Pattern::FloatLiteral( var_store.fresh(), var_store.fresh(), (parsed).into(), float, bound, ), }, 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((IntValue::U128(_), _)) if is_negative => { // Can't negate a u128; that doesn't fit in any integer literal type we support. let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } Ok((int, bound)) => { use std::ops::Neg; let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{sign_str}{int}").into_boxed_str(); let i = match int { // Safety: this is fine because I128::MAX = |I128::MIN| - 1 IntValue::I128(n) if is_negative => { IntValue::I128(i128::from_ne_bytes(n).neg().to_ne_bytes()) } IntValue::I128(n) => IntValue::I128(n), IntValue::U128(_) => unreachable!(), }; Pattern::IntLiteral(var_store.fresh(), var_store.fresh(), int_str, i, bound) } }, ptype => unsupported_pattern(env, ptype, region), }, StrLiteral(literal) => match pattern_type { WhenBranch => flatten_str_literal(literal), ptype => unsupported_pattern(env, ptype, region), }, SingleQuote(string) => { let mut it = string.chars().peekable(); if let Some(char) = it.next() { if it.peek().is_none() { Pattern::SingleQuote( var_store.fresh(), var_store.fresh(), char, SingleQuoteBound::from_char(char), ) } else { // multiple chars is found let problem = MalformedPatternProblem::MultipleCharsInSingleQuote; malformed_pattern(env, problem, region) } } else { // no characters found let problem = MalformedPatternProblem::EmptySingleQuote; malformed_pattern(env, problem, region) } } SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) => { return canonicalize_pattern( env, var_store, scope, output, pattern_type, sub_pattern, region, permit_shadows, ) } Tuple(patterns) => { let ext_var = var_store.fresh(); let whole_var = var_store.fresh(); let mut destructs = Vec::with_capacity(patterns.len()); for (i, loc_pattern) in patterns.iter().enumerate() { let can_guard = canonicalize_pattern( env, var_store, scope, output, pattern_type, &loc_pattern.value, loc_pattern.region, permit_shadows, ); destructs.push(Loc { region: loc_pattern.region, value: TupleDestruct { destruct_index: i, var: var_store.fresh(), typ: (var_store.fresh(), can_guard), }, }); } Pattern::TupleDestructure { whole_var, ext_var, destructs, } } 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.iter() { match loc_pattern.value { Identifier { ident: label } => { match scope.introduce(label.into(), region) { Ok(symbol) => { output.references.insert_bound(symbol); destructs.push(Loc { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), label: Lowercase::from(label), symbol, typ: DestructType::Required, }, }); } Err((shadowed_symbol, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region: shadowed_symbol.region, shadow: shadow.clone(), kind: ShadowKind::Variable, })); // 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( shadowed_symbol.region, shadow, new_symbol, )); } }; } RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! let symbol = scope.scopeless_symbol(&Ident::from(label), loc_pattern.region); let can_guard = canonicalize_pattern( env, var_store, scope, output, pattern_type, &loc_guard.value, loc_guard.region, permit_shadows, ); destructs.push(Loc { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), label: Lowercase::from(label), symbol, typ: DestructType::Guard(var_store.fresh(), can_guard), }, }); } OptionalField(label, loc_default) => { // an optional DOES introduce the label into scope! match scope.introduce(label.into(), region) { Ok(symbol) => { let (can_default, expr_output) = canonicalize_expr( env, var_store, scope, loc_default.region, &loc_default.value, ); // an optional field binds the symbol! output.references.insert_bound(symbol); output.union(expr_output); destructs.push(Loc { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), label: Lowercase::from(label), symbol, typ: DestructType::Optional(var_store.fresh(), can_default), }, }); } Err((shadowed_symbol, shadow, new_symbol)) => { env.problem(Problem::RuntimeError(RuntimeError::Shadowing { original_region: shadowed_symbol.region, shadow: shadow.clone(), kind: ShadowKind::Variable, })); // 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( shadowed_symbol.region, shadow, new_symbol, )); } }; } _ => 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(Pattern::RecordDestructure { whole_var, ext_var, destructs, }) } RequiredField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); } OptionalField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); } List(patterns) => { // We want to admit the following cases: // // [] // [..] // [.., P_1,* P_n] // [P_1,* P_n, ..] // [P_1,* P_m, .., P_n,* P_q] // [P_1,* P_n] // // So, a list-rest pattern can appear anywhere in a list pattern, but can appear at // most once. let elem_var = var_store.fresh(); let list_var = var_store.fresh(); let mut rest_index = None; let mut rest_name = None; let mut can_pats = Vec::with_capacity(patterns.len()); let mut opt_erroneous = None; for (i, loc_pattern) in patterns.iter().enumerate() { match &loc_pattern.value { ListRest(opt_pattern_as) => match rest_index { None => { rest_index = Some(i); if let Some((_, pattern_as)) = opt_pattern_as { match canonicalize_pattern_symbol( env, scope, output, region, permit_shadows, pattern_as.identifier.value, ) { Ok(symbol) => { rest_name = Some(Loc::at(pattern_as.identifier.region, symbol)); } Err(pattern) => { opt_erroneous = Some(pattern); } } } } Some(_) => { env.problem(Problem::MultipleListRestPattern { region: loc_pattern.region, }); opt_erroneous = Some(Pattern::MalformedPattern( MalformedPatternProblem::DuplicateListRestPattern, loc_pattern.region, )); } }, pattern => { let pat = canonicalize_pattern( env, var_store, scope, output, pattern_type, pattern, loc_pattern.region, permit_shadows, ); can_pats.push(pat); } } } // 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(Pattern::List { list_var, elem_var, patterns: ListPatterns { patterns: can_pats, opt_rest: rest_index.map(|i| (i, rest_name)), }, }) } ListRest(_opt_pattern_as) => { // Parsing should make sure these only appear in list patterns, where we will generate // better contextual errors. let problem = MalformedPatternProblem::Unknown; malformed_pattern(env, problem, region) } As(loc_pattern, pattern_as) => { let can_subpattern = canonicalize_pattern( env, var_store, scope, output, pattern_type, &loc_pattern.value, loc_pattern.region, permit_shadows, ); match canonicalize_pattern_symbol( env, scope, output, region, permit_shadows, pattern_as.identifier.value, ) { Ok(symbol) => Pattern::As(Box::new(can_subpattern), symbol), Err(pattern) => pattern, } } Malformed(_str) => { let problem = MalformedPatternProblem::Unknown; malformed_pattern(env, problem, region) } MalformedIdent(_str, problem) => { let problem = MalformedPatternProblem::BadIdent(*problem); malformed_pattern(env, problem, region) } QualifiedIdentifier { .. } => { let problem = MalformedPatternProblem::QualifiedIdentifier; malformed_pattern(env, problem, region) } }; Loc { 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(env: &mut Env, pattern_type: PatternType, region: Region) -> Pattern { use roc_problem::can::BadPattern; env.problem(Problem::UnsupportedPattern( BadPattern::Unsupported(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(env: &mut Env, problem: MalformedPatternProblem, region: Region) -> Pattern { env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( problem, region, ))); Pattern::MalformedPattern(problem, region) } /// An iterator over the bindings made by a pattern. /// /// We attempt to make no allocations when we can. pub enum BindingsFromPattern<'a> { Empty, One(&'a Loc), Many(Vec>), } pub enum BindingsFromPatternWork<'a> { Pattern(&'a Loc), RecordDestruct(&'a Loc), TupleDestruct(&'a Loc), } impl<'a> BindingsFromPattern<'a> { pub fn new(initial: &'a Loc) -> Self { Self::One(initial) } pub fn new_many(mut it: I) -> Self where I: Iterator>, { if let (1, Some(1)) = it.size_hint() { Self::new(it.next().unwrap()) } else { Self::Many(it.map(BindingsFromPatternWork::Pattern).collect()) } } fn next_many(stack: &mut Vec>) -> Option<(Symbol, Region)> { use Pattern::*; while let Some(work) = stack.pop() { match work { BindingsFromPatternWork::Pattern(loc_pattern) => { use BindingsFromPatternWork::*; match &loc_pattern.value { Identifier(symbol) | AbilityMemberSpecialization { ident: symbol, specializes: _, } => { return Some((*symbol, loc_pattern.region)); } As(pattern, symbol) => { stack.push(Pattern(pattern)); return Some((*symbol, loc_pattern.region)); } AppliedTag { arguments: loc_args, .. } => { let it = loc_args.iter().rev().map(|(_, p)| Pattern(p)); stack.extend(it); } UnwrappedOpaque { argument, .. } => { let (_, loc_arg) = &**argument; stack.push(Pattern(loc_arg)); } TupleDestructure { destructs, .. } => { let it = destructs.iter().rev().map(TupleDestruct); stack.extend(it); } RecordDestructure { destructs, .. } => { let it = destructs.iter().rev().map(RecordDestruct); stack.extend(it); } NumLiteral(..) | IntLiteral(..) | FloatLiteral(..) | StrLiteral(_) | SingleQuote(..) | Underscore | Shadowed(_, _, _) | MalformedPattern(_, _) | UnsupportedPattern(_) | OpaqueNotInScope(..) => (), List { patterns, .. } => { stack.extend(patterns.patterns.iter().rev().map(Pattern)); if let Some((_, Some(rest_sym))) = &patterns.opt_rest { return Some((rest_sym.value, rest_sym.region)); } } } } BindingsFromPatternWork::RecordDestruct(loc_destruct) => { match &loc_destruct.value.typ { DestructType::Required | DestructType::Optional(_, _) => { return Some((loc_destruct.value.symbol, loc_destruct.region)); } DestructType::Guard(_, inner) => { // a guard does not introduce the symbol stack.push(BindingsFromPatternWork::Pattern(inner)) } } } BindingsFromPatternWork::TupleDestruct(loc_destruct) => { let inner = &loc_destruct.value.typ.1; stack.push(BindingsFromPatternWork::Pattern(inner)) } } } None } } impl<'a> Iterator for BindingsFromPattern<'a> { type Item = (Symbol, Region); fn next(&mut self) -> Option { use Pattern::*; match self { BindingsFromPattern::Empty => None, BindingsFromPattern::One(loc_pattern) => match &loc_pattern.value { Identifier(symbol) | AbilityMemberSpecialization { ident: symbol, specializes: _, } => { let region = loc_pattern.region; *self = Self::Empty; Some((*symbol, region)) } _ => { *self = Self::Many(vec![BindingsFromPatternWork::Pattern(loc_pattern)]); self.next() } }, BindingsFromPattern::Many(stack) => Self::next_many(stack), } } } fn flatten_str_literal(literal: &StrLiteral<'_>) -> Pattern { use ast::StrLiteral::*; match literal { PlainLine(str_slice) => Pattern::StrLiteral((*str_slice).into()), Line(segments) => flatten_str_lines(&[segments]), Block(lines) => flatten_str_lines(lines), } } fn flatten_str_lines(lines: &[&[StrSegment<'_>]]) -> Pattern { use StrSegment::*; let mut buf = String::new(); for line in lines { for segment in line.iter() { match segment { Plaintext(string) => { buf.push_str(string); } Unicode(loc_digits) => { todo!("parse unicode digits {:?}", loc_digits); } Interpolated(loc_expr) | DeprecatedInterpolated(loc_expr) => { return Pattern::UnsupportedPattern(loc_expr.region); } EscapedChar(escaped) => buf.push(escaped.unescape()), } } } Pattern::StrLiteral(buf.into()) }