diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 0345412c98..d045c5fed4 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -8,7 +8,9 @@ use crate::env::Env; use crate::expr::{ ClosureData, DbgLookup, Declarations, ExpectLookup, Expr, Output, PendingDerives, }; -use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern, PermitShadows}; +use crate::pattern::{ + canonicalize_record_destructure, BindingsFromPattern, Pattern, PermitShadows, +}; use crate::procedure::References; use crate::scope::Scope; use bumpalo::Bump; @@ -149,7 +151,7 @@ pub struct RigidVariables { pub struct ModuleOutput { pub aliases: MutMap, pub rigid_variables: RigidVariables, - pub param_patterns: Vec, + pub params_pattern: Option, pub declarations: Declarations, pub exposed_imports: MutMap, pub exposed_symbols: VecSet, @@ -387,27 +389,25 @@ pub fn canonicalize_module_defs<'a>( let mut output = Output::default(); - let mut param_patterns = Vec::new(); - - if let Some(ModuleParams { pattern, .. }) = header_type.get_params() { - param_patterns.reserve_exact(pattern.value.len()); - - for param in pattern.value.iter() { - let pattern = canonicalize_pattern( + let params_pattern = header_type.get_params().as_ref().map( + |ModuleParams { + pattern, + before_arrow: _, + after_arrow: _, + }| { + canonicalize_record_destructure( &mut env, var_store, &mut scope, &mut output, // todo(agus): custom type for param PatternType::FunctionArg, - ¶m.value, - param.region, + &pattern.value, + pattern.region, PermitShadows(false), - ); - - param_patterns.push(pattern.value); - } - } + ) + }, + ); let (defs, output, symbols_introduced, imports_introduced) = canonicalize_defs( &mut env, @@ -839,7 +839,7 @@ pub fn canonicalize_module_defs<'a>( scope, aliases, rigid_variables, - param_patterns, + params_pattern, declarations, referenced_values, exposed_imports: can_exposed_imports, diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index dc3a7d7dfe..64e6b446ab 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -623,132 +623,16 @@ pub fn canonicalize_pattern<'a>( } } - 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, - }) - } + RecordDestructure(patterns) => canonicalize_record_destructure( + env, + var_store, + scope, + output, + pattern_type, + patterns, + region, + permit_shadows, + ), RequiredField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); @@ -894,6 +778,143 @@ pub fn canonicalize_pattern<'a>( } } +pub fn canonicalize_record_destructure<'a>( + env: &mut Env<'a>, + var_store: &mut VarStore, + scope: &mut Scope, + output: &mut Output, + pattern_type: PatternType, + patterns: &ast::Collection>>, + region: Region, + permit_shadows: PermitShadows, +) -> Pattern { + use ast::Pattern::*; + + 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, + }) +} + /// 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 {