Report unbound type variables in aliases, opaques

Closes #2726
This commit is contained in:
ayazhafiz 2022-03-14 17:42:45 -05:00
parent 17029cff7e
commit 3da34fc843
8 changed files with 246 additions and 50 deletions

View file

@ -29,25 +29,27 @@ pub struct IntroducedVariables {
// But then between annotations, the same name can occur multiple times, // But then between annotations, the same name can occur multiple times,
// but a variable can only have one name. Therefore // but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`. // `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>, pub wildcards: Vec<Loc<Variable>>,
pub lambda_sets: Vec<Variable>, pub lambda_sets: Vec<Variable>,
pub inferred: Vec<Variable>, pub inferred: Vec<Loc<Variable>>,
pub var_by_name: SendMap<Lowercase, Variable>, // NB: A mapping of a -> Loc<v1> in this map has the region of the first-seen var, but there
// may be multiple occurences of it!
pub var_by_name: SendMap<Lowercase, Loc<Variable>>,
pub name_by_var: SendMap<Variable, Lowercase>, pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>, pub host_exposed_aliases: MutMap<Symbol, Variable>,
} }
impl IntroducedVariables { impl IntroducedVariables {
pub fn insert_named(&mut self, name: Lowercase, var: Variable) { pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
self.var_by_name.insert(name.clone(), var); 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<Variable>) {
self.wildcards.push(var); self.wildcards.push(var);
} }
pub fn insert_inferred(&mut self, var: Variable) { pub fn insert_inferred(&mut self, var: Loc<Variable>) {
self.inferred.push(var); self.inferred.push(var);
} }
@ -70,7 +72,7 @@ impl IntroducedVariables {
} }
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { 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> { pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
@ -286,7 +288,7 @@ fn can_annotation_help(
let ret = can_annotation_help( let ret = can_annotation_help(
env, env,
&return_type.value, &return_type.value,
region, return_type.region,
scope, scope,
var_store, var_store,
introduced_variables, introduced_variables,
@ -314,7 +316,7 @@ fn can_annotation_help(
let arg_ann = can_annotation_help( let arg_ann = can_annotation_help(
env, env,
&arg.value, &arg.value,
region, arg.region,
scope, scope,
var_store, var_store,
introduced_variables, introduced_variables,
@ -368,7 +370,7 @@ fn can_annotation_help(
None => { None => {
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_named(name, var); introduced_variables.insert_named(name, Loc::at(region, var));
Type::Variable(var) Type::Variable(var)
} }
@ -377,7 +379,8 @@ fn can_annotation_help(
As( As(
loc_inner, loc_inner,
_spaces, _spaces,
alias_header @ TypeHeader { alias_header
@ TypeHeader {
name, name,
vars: loc_vars, vars: loc_vars,
}, },
@ -432,7 +435,8 @@ fn can_annotation_help(
} else { } else {
let var = var_store.fresh(); 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))); vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Loc::at(loc_var.region, (var_name, var))); lowercase_vars.push(Loc::at(loc_var.region, (var_name, var)));
@ -615,7 +619,7 @@ fn can_annotation_help(
Wildcard => { Wildcard => {
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_wildcard(var); introduced_variables.insert_wildcard(Loc::at(region, var));
Type::Variable(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 🤠 // make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_inferred(var); introduced_variables.insert_inferred(Loc::at(region, var));
Type::Variable(var) Type::Variable(var)
} }
@ -634,7 +638,7 @@ fn can_annotation_help(
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_wildcard(var); introduced_variables.insert_wildcard(Loc::at(region, var));
Type::Variable(var) Type::Variable(var)
} }
@ -700,7 +704,7 @@ fn can_extension_type<'a>(
let var = var_store.fresh(); let var = var_store.fresh();
introduced_variables.insert_inferred(var); introduced_variables.insert_inferred(Loc::at_zero(var));
Type::Variable(var) Type::Variable(var)
} }
@ -869,7 +873,10 @@ fn can_assigned_fields<'a>(
Type::Variable(*var) Type::Variable(*var)
} else { } else {
let field_var = var_store.fresh(); 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) Type::Variable(field_var)
} }
}; };

View file

@ -294,14 +294,12 @@ pub fn canonicalize_defs<'a>(
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len()); let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
let mut is_phantom = false; let mut is_phantom = false;
let mut var_by_name = can_ann.introduced_variables.var_by_name.clone();
for loc_lowercase in vars.iter() { for loc_lowercase in vars.iter() {
if let Some(var) = can_ann if let Some(var) = var_by_name.remove(&loc_lowercase.value) {
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the type def. // This is a valid lowercase rigid var for the type def.
can_vars.push(Loc { can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var), value: (loc_lowercase.value.clone(), var.value),
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
} else { } else {
@ -320,6 +318,32 @@ pub fn canonicalize_defs<'a>(
continue; 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( let alias = create_alias(
symbol, symbol,
name.region, name.region,

View file

@ -210,7 +210,7 @@ fn build_effect_always(
let signature = { let signature = {
// Effect.always : a -> Effect a // Effect.always : a -> Effect a
let var_a = var_store.fresh(); 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( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -223,7 +223,7 @@ fn build_effect_always(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
@ -402,8 +402,8 @@ fn build_effect_map(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_a = build_effect_alias( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -426,7 +426,7 @@ fn build_effect_map(
); );
let closure_var = var_store.fresh(); 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 = { let a_to_b = {
Type::Function( Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
@ -436,7 +436,7 @@ fn build_effect_map(
}; };
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![effect_a, a_to_b], vec![effect_a, a_to_b],
Box::new(Type::Variable(closure_var)), Box::new(Type::Variable(closure_var)),
@ -571,8 +571,8 @@ fn build_effect_after(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_a = build_effect_alias( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -595,7 +595,7 @@ fn build_effect_after(
); );
let closure_var = var_store.fresh(); 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( let a_to_effect_b = Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
Box::new(Type::Variable(closure_var)), Box::new(Type::Variable(closure_var)),
@ -603,7 +603,7 @@ fn build_effect_after(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![effect_a, a_to_effect_b], vec![effect_a, a_to_effect_b],
Box::new(Type::Variable(closure_var)), Box::new(Type::Variable(closure_var)),
@ -831,8 +831,8 @@ fn build_effect_forever(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_a = build_effect_alias( let effect_a = build_effect_alias(
effect_symbol, effect_symbol,
@ -855,7 +855,7 @@ fn build_effect_forever(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![effect_a], vec![effect_a],
@ -1089,8 +1089,8 @@ fn build_effect_loop(
let var_a = var_store.fresh(); let var_a = var_store.fresh();
let var_b = var_store.fresh(); let var_b = var_store.fresh();
introduced_variables.insert_named("a".into(), var_a); introduced_variables.insert_named("a".into(), Loc::at_zero(var_a));
introduced_variables.insert_named("b".into(), var_b); introduced_variables.insert_named("b".into(), Loc::at_zero(var_b));
let effect_b = build_effect_alias( let effect_b = build_effect_alias(
effect_symbol, effect_symbol,
@ -1117,7 +1117,7 @@ fn build_effect_loop(
let effect_state_type = { let effect_state_type = {
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
let actual = { let actual = {
Type::TagUnion( Type::TagUnion(
@ -1145,7 +1145,7 @@ fn build_effect_loop(
}; };
let closure_var = var_store.fresh(); 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( let step_type = Type::Function(
vec![Type::Variable(var_a)], vec![Type::Variable(var_a)],
@ -1154,7 +1154,7 @@ fn build_effect_loop(
); );
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
Type::Function( Type::Function(
vec![Type::Variable(var_a), step_type], vec![Type::Variable(var_a), step_type],
@ -1559,7 +1559,7 @@ fn build_effect_alias(
introduced_variables: &mut IntroducedVariables, introduced_variables: &mut IntroducedVariables,
) -> Type { ) -> Type {
let closure_var = var_store.fresh(); let closure_var = var_store.fresh();
introduced_variables.insert_wildcard(closure_var); introduced_variables.insert_wildcard(Loc::at_zero(closure_var));
let actual = { let actual = {
Type::TagUnion( Type::TagUnion(

View file

@ -259,7 +259,7 @@ pub fn canonicalize_module_defs<'a>(
} }
for var in output.introduced_variables.wildcards { for var in output.introduced_variables.wildcards {
rigid_variables.wildcards.insert(var); rigid_variables.wildcards.insert(var.value);
} }
let mut referenced_values = MutSet::default(); let mut referenced_values = MutSet::default();

View file

@ -1651,7 +1651,7 @@ fn instantiate_rigids(
headers: &mut SendMap<Symbol, Loc<Type>>, headers: &mut SendMap<Symbol, Loc<Type>>,
) -> InstantiateRigids { ) -> InstantiateRigids {
let mut annotation = annotation.clone(); let mut annotation = annotation.clone();
let mut new_rigid_variables = Vec::new(); let mut new_rigid_variables: Vec<Variable> = Vec::new();
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default(); let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
for (name, var) in introduced_vars.var_by_name.iter() { for (name, var) in introduced_vars.var_by_name.iter() {
@ -1660,23 +1660,24 @@ fn instantiate_rigids(
match ftv.entry(name.clone()) { match ftv.entry(name.clone()) {
Occupied(occupied) => { Occupied(occupied) => {
let existing_rigid = occupied.get(); let existing_rigid = occupied.get();
rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); rigid_substitution.insert(var.value, Type::Variable(*existing_rigid));
} }
Vacant(vacant) => { Vacant(vacant) => {
// It's possible to use this rigid in nested defs // It's possible to use this rigid in nested defs
vacant.insert(*var); vacant.insert(var.value);
new_rigid_variables.push(*var); new_rigid_variables.push(var.value);
} }
} }
} }
// wildcards are always freshly introduced in this annotation // 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 // lambda set vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied()); new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
let new_infer_variables = introduced_vars.inferred.clone(); let new_infer_variables: Vec<Variable> =
introduced_vars.inferred.iter().map(|v| v.value).collect();
// Instantiate rigid variables // Instantiate rigid variables
if !rigid_substitution.is_empty() { if !rigid_substitution.is_empty() {

View file

@ -43,6 +43,11 @@ pub enum Problem {
variable_region: Region, variable_region: Region,
variable_name: Lowercase, variable_name: Lowercase,
}, },
UnboundTypeVariable {
typ: Symbol,
num_unbound: usize,
one_occurence: Region,
},
DuplicateRecordFieldValue { DuplicateRecordFieldValue {
field_name: Lowercase, field_name: Lowercase,
record_region: Region, record_region: Region,

View file

@ -17,6 +17,7 @@ const UNRECOGNIZED_NAME: &str = "UNRECOGNIZED NAME";
const UNUSED_DEF: &str = "UNUSED DEFINITION"; const UNUSED_DEF: &str = "UNUSED DEFINITION";
const UNUSED_IMPORT: &str = "UNUSED IMPORT"; const UNUSED_IMPORT: &str = "UNUSED IMPORT";
const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER"; const UNUSED_ALIAS_PARAM: &str = "UNUSED TYPE ALIAS PARAMETER";
const UNBOUND_TYPE_VARIABLE: &str = "UNBOUND TYPE VARIABLE";
const UNUSED_ARG: &str = "UNUSED ARGUMENT"; const UNUSED_ARG: &str = "UNUSED ARGUMENT";
const MISSING_DEFINITION: &str = "MISSING DEFINITION"; const MISSING_DEFINITION: &str = "MISSING DEFINITION";
const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION"; const UNKNOWN_GENERATES_WITH: &str = "UNKNOWN GENERATES FUNCTION";
@ -252,6 +253,37 @@ pub fn can_problem<'b>(
title = UNUSED_ALIAS_PARAM.to_string(); title = UNUSED_ALIAS_PARAM.to_string();
severity = Severity::RuntimeError; 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) => { Problem::BadRecursion(entries) => {
doc = to_circular_def_doc(alloc, lines, &entries); doc = to_circular_def_doc(alloc, lines, &entries);
title = CIRCULAR_DEF.to_string(); title = CIRCULAR_DEF.to_string();

View file

@ -76,7 +76,7 @@ mod test_reporting {
} }
for var in output.introduced_variables.wildcards { for var in output.introduced_variables.wildcards {
subs.rigid_var(var, "*".into()); subs.rigid_var(var.value, "*".into());
} }
let mut unify_problems = Vec::new(); 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?
"#
),
)
}
} }