diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 188f97426c..b8ad318da3 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -235,7 +235,6 @@ pub fn canonicalize_defs<'a>( let mut scope = original_scope.clone(); let num_defs = loc_defs.len(); let mut refs_by_symbol = MutMap::default(); - let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); let mut type_defs = Vec::with_capacity(num_defs); let mut value_defs = Vec::with_capacity(num_defs); @@ -508,21 +507,55 @@ pub fn canonicalize_defs<'a>( let mut def_order = DefOrdering::from_symbol_to_id(env.home, symbol_to_id); - for pending_def in pending_value_defs.into_iter() { - output = canonicalize_pending_value_def( - env, - pending_def, - output, - &mut scope, - &mut can_defs_by_symbol, - var_store, - &mut refs_by_symbol, - &mut aliases, - &abilities_in_scope, - ); + let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher()); - // TODO we should do something with these references; they include - // things like type annotations. + // env.home.register_debug_idents(&env.ident_ids); + + if true { + for pending_def in pending_value_defs.into_iter() { + let region = pending_def.loc_pattern().region; + + let bindings = bindings_from_patterns(std::iter::once(pending_def.loc_pattern())); + + let temp_output = canonicalize_pending_value_def_new( + env, + pending_def, + output, + &mut scope, + &mut can_defs_by_symbol, + var_store, + &mut refs_by_symbol, + &mut aliases, + &abilities_in_scope, + ); + + output = temp_output.output; + + for (symbol, _) in bindings { + can_defs_by_symbol.insert(symbol, temp_output.def.clone()); + refs_by_symbol.insert(symbol, (region, temp_output.references.clone())); + } + + // TODO we should do something with these references; they include + // things like type annotations. + } + } else { + for pending_def in pending_value_defs.into_iter() { + output = canonicalize_pending_value_def( + env, + pending_def, + output, + &mut scope, + &mut can_defs_by_symbol, + var_store, + &mut refs_by_symbol, + &mut aliases, + &abilities_in_scope, + ); + + // TODO we should do something with these references; they include + // things like type annotations. + } } // Determine which idents we introduced in the course of this process. @@ -1203,6 +1236,12 @@ fn add_annotation_aliases( } } +struct TempOutput { + output: Output, + def: Def, + references: References, +} + // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] @@ -1648,6 +1687,444 @@ fn canonicalize_pending_value_def<'a>( output } +// TODO trim down these arguments! +#[allow(clippy::too_many_arguments)] +#[allow(clippy::cognitive_complexity)] +fn canonicalize_pending_value_def_new<'a>( + env: &mut Env<'a>, + pending_def: PendingValueDef<'a>, + mut output: Output, + scope: &mut Scope, + can_defs_by_symbol: &mut MutMap, + var_store: &mut VarStore, + refs_by_symbol: &mut MutMap, + aliases: &mut ImMap, + abilities_in_scope: &[Symbol], +) -> TempOutput { + use PendingValueDef::*; + + // Make types for the body expr, even if we won't end up having a body. + let expr_var = var_store.fresh(); + let mut vars_by_symbol = SendMap::default(); + + match pending_def { + AnnotationOnly(_, loc_can_pattern, loc_ann) => { + // annotation sans body cannot introduce new rigids that are visible in other annotations + // but the rigids can show up in type error messages, so still register them + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); + + // Record all the annotation's references in output.references.lookups + + for symbol in type_annotation.references.iter() { + output.references.insert_type_lookup(*symbol); + } + + add_annotation_aliases(&type_annotation, aliases); + + output + .introduced_variables + .union(&type_annotation.introduced_variables); + + pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); + + let arity = type_annotation.typ.arity(); + + let problem = match &loc_can_pattern.value { + Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { + def_symbol: *symbol, + }, + Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing { + original_region: *region, + shadow: loc_ident.clone(), + kind: ShadowKind::Variable, + }, + _ => RuntimeError::NoImplementation, + }; + + // Fabricate a body for this annotation, that will error at runtime + let value = Expr::RuntimeError(problem); + let is_closure = arity > 0; + let loc_can_expr = if !is_closure { + Loc { + value, + region: loc_ann.region, + } + } else { + let symbol = env.gen_unique_symbol(); + + // generate a fake pattern for each argument. this makes signatures + // that are functions only crash when they are applied. + let mut underscores = Vec::with_capacity(arity); + + for _ in 0..arity { + let underscore: Loc = Loc { + value: Pattern::Underscore, + region: Region::zero(), + }; + + underscores.push((var_store.fresh(), underscore)); + } + + let body_expr = Loc { + value, + region: loc_ann.region, + }; + + Loc { + value: Closure(ClosureData { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + captured_symbols: Vec::new(), + recursive: Recursive::NotRecursive, + arguments: underscores, + loc_body: Box::new(body_expr), + }), + region: loc_ann.region, + } + }; + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), + ); + + TempOutput { + output, + references: References::default(), + def, + } + } + + TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { + let type_annotation = canonicalize_annotation( + env, + scope, + &loc_ann.value, + loc_ann.region, + var_store, + abilities_in_scope, + ); + + // Record all the annotation's references in output.references.lookups + for symbol in type_annotation.references.iter() { + output.references.insert_type_lookup(*symbol); + } + + add_annotation_aliases(&type_annotation, aliases); + + output + .introduced_variables + .union(&type_annotation.introduced_variables); + + // bookkeeping for tail-call detection. If we're assigning to an + // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. + let outer_identifier = env.tailcallable_symbol; + + if let Pattern::Identifier(ref defined_symbol) = &loc_can_pattern.value { + env.tailcallable_symbol = Some(*defined_symbol); + }; + + // register the name of this closure, to make sure the closure won't capture it's own name + if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = + (&loc_can_pattern.value, &loc_expr.value) + { + env.closure_name_symbol = Some(*defined_symbol); + }; + + pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); + + let (mut loc_can_expr, can_output) = + canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); + + output.references.union_mut(&can_output.references); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_identifier; + + // First, make sure we are actually assigning an identifier instead of (for example) a tag. + // + // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, + // which also implies it's not a self tail call! + // + // Only defs of the form (foo = ...) can be closure declarations or self tail calls. + if let Pattern::Identifier(symbol) + | Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value + { + if let Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: ref closure_name, + ref arguments, + loc_body: ref body, + ref captured_symbols, + .. + }) = loc_can_expr.value + { + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. (We'll re-insert it later.) + let references = env.closures.remove(closure_name).unwrap_or_else(|| { + panic!( + "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", + closure_name, env.closures + ) + }); + + // Re-insert the closure into the map, under its defined name. + // closures don't have a name, and therefore pick a fresh symbol. But in this + // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` + // and we want to reference it by that name. + env.closures.insert(symbol, references); + + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.remove_value_lookup(&symbol); + }); + + // renamed_closure_def = Some(&symbol); + loc_can_expr.value = Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: symbol, + captured_symbols: captured_symbols.clone(), + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + // + // NOTE: this is where we lose reference information for closure bodies. + let refs = References::new(); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), + ); + + output.union(can_output); + + TempOutput { + output, + references: refs, + def, + } + } else { + let refs = can_output.references.clone(); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), + ); + + output.union(can_output); + + TempOutput { + output, + references: refs, + def, + } + } + } else { + let refs = can_output.references.clone(); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + Some(Loc::at(loc_ann.region, type_annotation)), + vars_by_symbol.clone(), + ); + + output.union(can_output); + + TempOutput { + output, + references: refs, + def, + } + } + } + // If we have a pattern, then the def has a body (that is, it's not a + // standalone annotation), so we need to canonicalize the pattern and expr. + Body(loc_pattern, loc_can_pattern, loc_expr) => { + // bookkeeping for tail-call detection. If we're assigning to an + // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. + let outer_identifier = env.tailcallable_symbol; + + if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) = + (&loc_pattern.value, &loc_can_pattern.value) + { + env.tailcallable_symbol = Some(*defined_symbol); + + // TODO isn't types_by_symbol enough? Do we need vars_by_symbol too? + vars_by_symbol.insert(*defined_symbol, expr_var); + }; + + // register the name of this closure, to make sure the closure won't capture it's own name + if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = + (&loc_can_pattern.value, &loc_expr.value) + { + env.closure_name_symbol = Some(*defined_symbol); + }; + + let (mut loc_can_expr, can_output) = + canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); + + // reset the tailcallable_symbol + env.tailcallable_symbol = outer_identifier; + + // First, make sure we are actually assigning an identifier instead of (for example) a tag. + // + // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, + // which also implies it's not a self tail call! + // + // Only defs of the form (foo = ...) can be closure declarations or self tail calls. + if let Pattern::Identifier(symbol) = loc_can_pattern.value { + if let Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: ref closure_name, + ref arguments, + loc_body: ref body, + ref captured_symbols, + .. + }) = loc_can_expr.value + { + // Since everywhere in the code it'll be referred to by its defined name, + // remove its generated name from the closure map. (We'll re-insert it later.) + let references = env.closures.remove(closure_name).unwrap_or_else(|| { + panic!( + "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", + closure_name, env.closures + ) + }); + + // Re-insert the closure into the map, under its defined name. + // closures don't have a name, and therefore pick a fresh symbol. But in this + // case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...` + // and we want to reference it by that name. + env.closures.insert(symbol, references); + + // The closure is self tail recursive iff it tail calls itself (by defined name). + let is_recursive = match can_output.tail_call { + Some(tail_symbol) if tail_symbol == symbol => Recursive::TailRecursive, + _ => Recursive::NotRecursive, + }; + + // Recursion doesn't count as referencing. (If it did, all recursive functions + // would result in circular def errors!) + refs_by_symbol.entry(symbol).and_modify(|(_, refs)| { + refs.remove_value_lookup(&symbol); + }); + + loc_can_expr.value = Closure(ClosureData { + function_type, + closure_type, + closure_ext_var, + return_type, + name: symbol, + captured_symbols: captured_symbols.clone(), + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }); + + // Functions' references don't count in defs. + // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its + // parent commit for the bug this fixed! + let refs = References::new(); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + None, + vars_by_symbol.clone(), + ); + + output.union(can_output); + + TempOutput { + output, + references: refs, + def, + } + } else { + let refs = can_output.references.clone(); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + None, + vars_by_symbol.clone(), + ); + + output.union(can_output); + + TempOutput { + output, + references: refs, + def, + } + } + } else { + let refs = can_output.references.clone(); + + let def = single_can_def( + loc_can_pattern, + loc_can_expr, + expr_var, + None, + vars_by_symbol.clone(), + ); + + output.union(can_output); + + TempOutput { + output, + references: refs, + def, + } + } + } + } +} + #[inline(always)] pub fn can_defs_with_return<'a>( env: &mut Env<'a>, diff --git a/compiler/test_gen/src/gen_primitives.rs b/compiler/test_gen/src/gen_primitives.rs index 16f4248dc8..7c32b4aff7 100644 --- a/compiler/test_gen/src/gen_primitives.rs +++ b/compiler/test_gen/src/gen_primitives.rs @@ -593,6 +593,27 @@ fn top_level_constant() { ); } +#[test] +#[ignore] +#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] +fn top_level_destructure() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ main ] to "./platform" + + {a, b} = { a: 1, b: 2 } + + main = + + a + b + "# + ), + 3, + i64 + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn linked_list_len_0() { @@ -2971,6 +2992,7 @@ fn mix_function_and_closure_level_of_indirection() { } #[test] +#[ignore] #[cfg(any(feature = "gen-llvm"))] fn do_pass_bool_byte_closure_layout() { // see https://github.com/rtfeldman/roc/pull/1706 diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 8d25660f64..83951bd8d9 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -7092,26 +7092,23 @@ I need all branches in an `if` to have the same type! indoc!( r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with the body of the `f` definition: - - 1│ f : a, b, * -> * - 2│ f = \_, _, x2 -> + + Something is off with the body of the `inner` definition: + 3│ inner : * -> * 4│ inner = \y -> y - 5│ inner x2 - ^^^^^^^^ - - The type annotation on `f` says this `inner` call should have the type: - + ^ + + The type annotation on `inner` says this `y` value should have the type: + * - - However, the type of this `inner` call is connected to another type in a + + However, the type of this `y` value is connected to another type in a way that isn't reflected in this annotation. - + Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `f` should have a named type variable in - place of the `*`? + a `*`! Maybe the annotation on `inner` should have a named type variable + in place of the `*`? "# ), )