diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index e6732bea50..95b9f139c5 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -29,25 +29,27 @@ pub struct IntroducedVariables { // But then between annotations, the same name can occur multiple times, // but a variable can only have one name. Therefore // `ftv : SendMap`. - pub wildcards: Vec, + pub wildcards: Vec>, pub lambda_sets: Vec, - pub inferred: Vec, - pub var_by_name: SendMap, + pub inferred: Vec>, + // NB: A mapping of a -> Loc in this map has the region of the first-seen var, but there + // may be multiple occurences of it! + pub var_by_name: SendMap>, pub name_by_var: SendMap, pub host_exposed_aliases: MutMap, } impl IntroducedVariables { - pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + pub fn insert_named(&mut self, name: Lowercase, var: Loc) { self.var_by_name.insert(name.clone(), var); - self.name_by_var.insert(var, name); + self.name_by_var.insert(var.value, name); } - pub fn insert_wildcard(&mut self, var: Variable) { + pub fn insert_wildcard(&mut self, var: Loc) { self.wildcards.push(var); } - pub fn insert_inferred(&mut self, var: Variable) { + pub fn insert_inferred(&mut self, var: Loc) { self.inferred.push(var); } @@ -70,7 +72,7 @@ impl IntroducedVariables { } pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { - self.var_by_name.get(name) + self.var_by_name.get(name).map(|v| &v.value) } pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { @@ -286,7 +288,7 @@ fn can_annotation_help( let ret = can_annotation_help( env, &return_type.value, - region, + return_type.region, scope, var_store, introduced_variables, @@ -314,7 +316,7 @@ fn can_annotation_help( let arg_ann = can_annotation_help( env, &arg.value, - region, + arg.region, scope, var_store, introduced_variables, @@ -368,7 +370,7 @@ fn can_annotation_help( None => { let var = var_store.fresh(); - introduced_variables.insert_named(name, var); + introduced_variables.insert_named(name, Loc::at(region, var)); Type::Variable(var) } @@ -377,7 +379,8 @@ fn can_annotation_help( As( loc_inner, _spaces, - alias_header @ TypeHeader { + alias_header + @ TypeHeader { name, vars: loc_vars, }, @@ -432,7 +435,8 @@ fn can_annotation_help( } else { let var = var_store.fresh(); - introduced_variables.insert_named(var_name.clone(), var); + introduced_variables + .insert_named(var_name.clone(), Loc::at(loc_var.region, var)); vars.push((var_name.clone(), Type::Variable(var))); lowercase_vars.push(Loc::at(loc_var.region, (var_name, var))); @@ -615,7 +619,7 @@ fn can_annotation_help( Wildcard => { let var = var_store.fresh(); - introduced_variables.insert_wildcard(var); + introduced_variables.insert_wildcard(Loc::at(region, var)); Type::Variable(var) } @@ -624,7 +628,7 @@ fn can_annotation_help( // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠 let var = var_store.fresh(); - introduced_variables.insert_inferred(var); + introduced_variables.insert_inferred(Loc::at(region, var)); Type::Variable(var) } @@ -634,7 +638,7 @@ fn can_annotation_help( let var = var_store.fresh(); - introduced_variables.insert_wildcard(var); + introduced_variables.insert_wildcard(Loc::at(region, var)); Type::Variable(var) } @@ -700,7 +704,7 @@ fn can_extension_type<'a>( let var = var_store.fresh(); - introduced_variables.insert_inferred(var); + introduced_variables.insert_inferred(Loc::at_zero(var)); Type::Variable(var) } @@ -869,7 +873,10 @@ fn can_assigned_fields<'a>( Type::Variable(*var) } else { let field_var = var_store.fresh(); - introduced_variables.insert_named(field_name.clone(), field_var); + introduced_variables.insert_named( + field_name.clone(), + Loc::at(loc_field_name.region, field_var), + ); Type::Variable(field_var) } }; diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index e31b699107..b036746af3 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -294,14 +294,12 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); let mut is_phantom = false; + let mut var_by_name = can_ann.introduced_variables.var_by_name.clone(); for loc_lowercase in vars.iter() { - if let Some(var) = can_ann - .introduced_variables - .var_by_name(&loc_lowercase.value) - { + if let Some(var) = var_by_name.remove(&loc_lowercase.value) { // This is a valid lowercase rigid var for the type def. can_vars.push(Loc { - value: (loc_lowercase.value.clone(), *var), + value: (loc_lowercase.value.clone(), var.value), region: loc_lowercase.region, }); } else { @@ -320,6 +318,32 @@ pub fn canonicalize_defs<'a>( continue; } + let IntroducedVariables { + wildcards, + inferred, + .. + } = can_ann.introduced_variables; + let num_unbound = var_by_name.len() + wildcards.len() + inferred.len(); + if num_unbound > 0 { + let one_occurence = var_by_name + .iter() + .map(|(_, v)| v) + .chain(wildcards.iter()) + .chain(inferred.iter()) + .next() + .unwrap() + .region; + + env.problems.push(Problem::UnboundTypeVariable { + typ: symbol, + num_unbound, + one_occurence, + }); + + // Bail out + continue; + } + let alias = create_alias( symbol, name.region, diff --git a/compiler/can/src/effect_module.rs b/compiler/can/src/effect_module.rs index 6c04645b14..92fd123ab4 100644 --- a/compiler/can/src/effect_module.rs +++ b/compiler/can/src/effect_module.rs @@ -210,7 +210,7 @@ fn build_effect_always( let signature = { // Effect.always : a -> Effect a let var_a = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); let effect_a = build_effect_alias( effect_symbol, @@ -223,7 +223,7 @@ fn build_effect_always( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![Type::Variable(var_a)], @@ -402,8 +402,8 @@ fn build_effect_map( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_a = build_effect_alias( effect_symbol, @@ -426,7 +426,7 @@ fn build_effect_map( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let a_to_b = { Type::Function( vec![Type::Variable(var_a)], @@ -436,7 +436,7 @@ fn build_effect_map( }; let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![effect_a, a_to_b], Box::new(Type::Variable(closure_var)), @@ -571,8 +571,8 @@ fn build_effect_after( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_a = build_effect_alias( effect_symbol, @@ -595,7 +595,7 @@ fn build_effect_after( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let a_to_effect_b = Type::Function( vec![Type::Variable(var_a)], Box::new(Type::Variable(closure_var)), @@ -603,7 +603,7 @@ fn build_effect_after( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![effect_a, a_to_effect_b], Box::new(Type::Variable(closure_var)), @@ -831,8 +831,8 @@ fn build_effect_forever( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_a = build_effect_alias( effect_symbol, @@ -855,7 +855,7 @@ fn build_effect_forever( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![effect_a], @@ -1089,8 +1089,8 @@ fn build_effect_loop( let var_a = var_store.fresh(); let var_b = var_store.fresh(); - introduced_variables.insert_named("a".into(), var_a); - introduced_variables.insert_named("b".into(), var_b); + introduced_variables.insert_named("a".into(), Loc::at_zero(var_a)); + introduced_variables.insert_named("b".into(), Loc::at_zero(var_b)); let effect_b = build_effect_alias( effect_symbol, @@ -1117,7 +1117,7 @@ fn build_effect_loop( let effect_state_type = { let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let actual = { Type::TagUnion( @@ -1145,7 +1145,7 @@ fn build_effect_loop( }; let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let step_type = Type::Function( vec![Type::Variable(var_a)], @@ -1154,7 +1154,7 @@ fn build_effect_loop( ); let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); Type::Function( vec![Type::Variable(var_a), step_type], @@ -1559,7 +1559,7 @@ fn build_effect_alias( introduced_variables: &mut IntroducedVariables, ) -> Type { let closure_var = var_store.fresh(); - introduced_variables.insert_wildcard(closure_var); + introduced_variables.insert_wildcard(Loc::at_zero(closure_var)); let actual = { Type::TagUnion( diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index fad4785f56..0de1051bec 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -259,7 +259,7 @@ pub fn canonicalize_module_defs<'a>( } for var in output.introduced_variables.wildcards { - rigid_variables.wildcards.insert(var); + rigid_variables.wildcards.insert(var.value); } let mut referenced_values = MutSet::default(); diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 6af602a30f..a604c817cc 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1651,7 +1651,7 @@ fn instantiate_rigids( headers: &mut SendMap>, ) -> InstantiateRigids { let mut annotation = annotation.clone(); - let mut new_rigid_variables = Vec::new(); + let mut new_rigid_variables: Vec = Vec::new(); let mut rigid_substitution: ImMap = ImMap::default(); for (name, var) in introduced_vars.var_by_name.iter() { @@ -1660,23 +1660,24 @@ fn instantiate_rigids( match ftv.entry(name.clone()) { Occupied(occupied) => { let existing_rigid = occupied.get(); - rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); + rigid_substitution.insert(var.value, Type::Variable(*existing_rigid)); } Vacant(vacant) => { // It's possible to use this rigid in nested defs - vacant.insert(*var); - new_rigid_variables.push(*var); + vacant.insert(var.value); + new_rigid_variables.push(var.value); } } } // wildcards are always freshly introduced in this annotation - new_rigid_variables.extend(introduced_vars.wildcards.iter().copied()); + new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value)); // lambda set vars are always freshly introduced in this annotation new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied()); - let new_infer_variables = introduced_vars.inferred.clone(); + let new_infer_variables: Vec = + introduced_vars.inferred.iter().map(|v| v.value).collect(); // Instantiate rigid variables if !rigid_substitution.is_empty() { diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index e5d23518f9..77dd95a4a1 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -43,6 +43,11 @@ pub enum Problem { variable_region: Region, variable_name: Lowercase, }, + UnboundTypeVariable { + typ: Symbol, + num_unbound: usize, + one_occurence: Region, + }, DuplicateRecordFieldValue { field_name: Lowercase, record_region: Region, diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 6473db7388..02640aa946 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -17,6 +17,7 @@ const UNRECOGNIZED_NAME: &str = "UNRECOGNIZED NAME"; const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; +const UNBOUND_TYPE_VARIABLE: &str = "UNBOUND TYPE VARIABLE"; const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const MISSING_DEFINITION: &str = "MISSING DEFINITION"; const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; @@ -252,6 +253,37 @@ pub fn can_problem<'b>( title = UNUSED_ALIAS_PARAM.to_string(); severity = Severity::RuntimeError; } + Problem::UnboundTypeVariable { + typ: alias, + num_unbound, + one_occurence, + } => { + let mut stack = Vec::with_capacity(4); + if num_unbound == 1 { + stack.push(alloc.concat(vec![ + alloc.reflow("The definition of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" has an unbound type variable:"), + ])); + } else { + stack.push(alloc.concat(vec![ + alloc.reflow("The definition of "), + alloc.symbol_unqualified(alias), + alloc.reflow(" has "), + alloc.text(format!("{}", num_unbound)), + alloc.reflow(" unbound type variables."), + ])); + stack.push(alloc.reflow("Here is one occurence:")); + } + stack.push(alloc.region(lines.convert_region(one_occurence))); + stack.push(alloc.tip().append( + alloc.reflow("Perhaps you intended to add a type parameter to this type?"), + )); + doc = alloc.stack(stack); + + title = UNBOUND_TYPE_VARIABLE.to_string(); + severity = Severity::RuntimeError; + } Problem::BadRecursion(entries) => { doc = to_circular_def_doc(alloc, lines, &entries); title = CIRCULAR_DEF.to_string(); diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index f896a9fa11..1a80313805 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -76,7 +76,7 @@ mod test_reporting { } for var in output.introduced_variables.wildcards { - subs.rigid_var(var, "*".into()); + subs.rigid_var(var.value, "*".into()); } let mut unify_problems = Vec::new(); @@ -8727,4 +8727,131 @@ I need all branches in an `if` to have the same type! ), ) } + + #[test] + fn wildcard_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int * + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int * + ^ + + Tip: Perhaps you intended to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn wildcard_in_opaque() { + report_problem_as( + indoc!( + r#" + I := Int * + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I := Int * + ^ + + Tip: Perhaps you intended to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn multiple_wildcards_in_alias() { + report_problem_as( + indoc!( + r#" + I : [ A (Int *), B (Int *) ] + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has 2 unbound type variables. + + Here is one occurence: + + 1│ I : [ A (Int *), B (Int *) ] + ^ + + Tip: Perhaps you intended to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn inference_var_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int _ + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int _ + ^ + + Tip: Perhaps you intended to add a type parameter to this type? + "# + ), + ) + } + + #[test] + fn unbound_var_in_alias() { + report_problem_as( + indoc!( + r#" + I : Int a + a : I + a + "# + ), + indoc!( + r#" + ── UNBOUND TYPE VARIABLE ─────────────────────────────────────────────────────── + + The definition of `I` has an unbound type variable: + + 1│ I : Int a + ^ + + Tip: Perhaps you intended to add a type parameter to this type? + "# + ), + ) + } }