Revise errors for undeclared type vars in aliases

This commit is contained in:
Richard Feldman 2024-05-12 21:20:11 -04:00
parent e5ea6dc461
commit 8357bd8c91
No known key found for this signature in database
GPG key ID: F1F21AA5B1D9E43B
4 changed files with 207 additions and 39 deletions

View file

@ -439,28 +439,43 @@ fn canonicalize_alias<'a>(
return Err(()); return Err(());
} }
let num_unbound = named.len() + wildcards.len() + inferred.len(); // Report errors for wildcards (*), underscores (_), and named vars that weren't declared.
if num_unbound > 0 { let mut no_problems = true;
let one_occurrence = named
.iter()
.map(|nv| Loc::at(nv.first_seen(), nv.variable()))
.chain(wildcards)
.chain(inferred)
.next()
.unwrap()
.region;
env.problems.push(Problem::UnboundTypeVariable { if let Some(loc_var) = wildcards.first() {
env.problems.push(Problem::WildcardNotAllowed {
typ: symbol, typ: symbol,
num_unbound, num_wildcards: wildcards.len(),
one_occurrence, one_occurrence: loc_var.region,
kind, kind,
}); });
// Bail out no_problems = false;
return Err(());
} }
if let Some(loc_var) = inferred.first() {
env.problems.push(Problem::UnderscoreNotAllowed {
typ: symbol,
num_underscores: inferred.len(),
one_occurrence: loc_var.region,
kind,
});
no_problems = false;
}
if let Some(nv) = named.first() {
env.problems.push(Problem::UndeclaredTypeVar {
typ: symbol,
num_unbound: named.len(),
one_occurrence: nv.first_seen(),
kind,
});
no_problems = false;
}
if no_problems {
Ok(create_alias( Ok(create_alias(
symbol, symbol,
name.region, name.region,
@ -469,6 +484,9 @@ fn canonicalize_alias<'a>(
can_ann.typ, can_ann.typ,
kind, kind,
)) ))
} else {
Err(())
}
} }
/// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`. /// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`.

View file

@ -8651,6 +8651,29 @@ In roc, functions are always written as a lambda, like{}
" "
); );
test_report!(
underscore_in_alias,
indoc!(
r"
I : Num.Int _
a : I
a = 0x5
a
"
),
@r"
UNBOUND TYPE VARIABLE in /code/proj/Main.roc
The definition of `I` has an unbound type variable:
4 I : Num.Int _
^
Tip: Type variables must be bound before the `:`. Perhaps you intended
to add a type parameter to this type?
"
);
test_report!( test_report!(
wildcard_in_opaque, wildcard_in_opaque,
indoc!( indoc!(

View file

@ -76,7 +76,7 @@ pub enum Problem {
variable_name: Lowercase, variable_name: Lowercase,
alias_kind: AliasKind, alias_kind: AliasKind,
}, },
UnboundTypeVariable { UndeclaredTypeVar {
typ: Symbol, typ: Symbol,
num_unbound: usize, num_unbound: usize,
one_occurrence: Region, one_occurrence: Region,
@ -99,7 +99,6 @@ pub enum Problem {
record_region: Region, record_region: Region,
field_region: Region, field_region: Region,
}, },
DuplicateTag { DuplicateTag {
tag_name: TagName, tag_name: TagName,
tag_union_region: Region, tag_union_region: Region,
@ -225,6 +224,18 @@ pub enum Problem {
filename: PathBuf, filename: PathBuf,
error: io::ErrorKind, error: io::ErrorKind,
}, },
WildcardNotAllowed {
typ: Symbol,
num_wildcards: usize,
one_occurrence: Region,
kind: AliasKind,
},
UnderscoreNotAllowed {
typ: Symbol,
num_underscores: usize,
one_occurrence: Region,
kind: AliasKind,
},
} }
impl Problem { impl Problem {
@ -249,7 +260,9 @@ impl Problem {
Problem::CyclicAlias(..) => RuntimeError, Problem::CyclicAlias(..) => RuntimeError,
Problem::BadRecursion(_) => RuntimeError, Problem::BadRecursion(_) => RuntimeError,
Problem::PhantomTypeArgument { .. } => Warning, Problem::PhantomTypeArgument { .. } => Warning,
Problem::UnboundTypeVariable { .. } => RuntimeError, Problem::UndeclaredTypeVar { .. } => RuntimeError,
Problem::WildcardNotAllowed { .. } => RuntimeError,
Problem::UnderscoreNotAllowed { .. } => RuntimeError,
Problem::DuplicateRecordFieldValue { .. } => Warning, Problem::DuplicateRecordFieldValue { .. } => Warning,
Problem::DuplicateRecordFieldType { .. } => RuntimeError, Problem::DuplicateRecordFieldType { .. } => RuntimeError,
Problem::InvalidOptionalValue { .. } => RuntimeError, Problem::InvalidOptionalValue { .. } => RuntimeError,
@ -330,7 +343,15 @@ impl Problem {
variable_region: region, variable_region: region,
.. ..
} }
| Problem::UnboundTypeVariable { | Problem::WildcardNotAllowed {
one_occurrence: region,
..
}
| Problem::UnderscoreNotAllowed {
one_occurrence: region,
..
}
| Problem::UndeclaredTypeVar {
one_occurrence: region, one_occurrence: region,
.. ..
} }

View file

@ -23,7 +23,9 @@ const UNUSED_IMPORT: &str = "UNUSED IMPORT";
const IMPORT_NAME_CONFLICT: &str = "IMPORT NAME CONFLICT"; const IMPORT_NAME_CONFLICT: &str = "IMPORT NAME CONFLICT";
const EXPLICIT_BUILTIN_IMPORT: &str = "EXPLICIT BUILTIN IMPORT"; const EXPLICIT_BUILTIN_IMPORT: &str = "EXPLICIT BUILTIN 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 UNDECLARED_TYPE_VARIABLE: &str = "UNDECLARED TYPE VARIABLE";
const WILDCARD_NOT_ALLOWED: &str = "WILDCARD NOT ALLOWED HERE";
const UNDERSCORE_NOT_ALLOWED: &str = "UNDERSCORE NOT ALLOWED HERE";
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";
@ -457,41 +459,145 @@ pub fn can_problem<'b>(
title = UNUSED_ALIAS_PARAM.to_string(); title = UNUSED_ALIAS_PARAM.to_string();
} }
Problem::UnboundTypeVariable { Problem::WildcardNotAllowed {
typ: alias, typ: alias,
num_unbound, num_wildcards,
one_occurrence, one_occurrence,
kind, kind,
} => { } => {
let mut stack = Vec::with_capacity(4); let mut stack = Vec::with_capacity(4);
if num_unbound == 1 { if num_wildcards == 1 {
stack.push(alloc.concat([ stack.push(alloc.concat([
alloc.reflow("The definition of "), alloc.reflow("The definition of "),
alloc.symbol_unqualified(alias), alloc.symbol_unqualified(alias),
alloc.reflow(" has an unbound type variable:"), alloc.reflow(" includes a wildcard ("),
alloc.keyword("*"),
alloc.reflow(") type variable:"),
])); ]));
} else { } else {
stack.push(alloc.concat([ stack.push(alloc.concat([
alloc.reflow("The definition of "), alloc.reflow("The definition of "),
alloc.symbol_unqualified(alias), alloc.symbol_unqualified(alias),
alloc.reflow(" has "), alloc.reflow(" includes "),
text!(alloc, "{}", num_unbound), text!(alloc, "{}", num_wildcards),
alloc.reflow(" unbound type variables."), alloc.reflow(" wildcard ("),
alloc.keyword("*"),
alloc.reflow(") type variables:"),
])); ]));
stack.push(alloc.reflow("Here is one occurrence:")); stack.push(alloc.reflow("Here is one of them:"));
} }
stack.push(alloc.region(lines.convert_region(one_occurrence))); stack.push(alloc.region(lines.convert_region(one_occurrence)));
stack.push(alloc.tip().append(alloc.concat([ stack.push(alloc.tip().append(alloc.concat([
alloc.reflow("Type variables must be bound before the "), alloc.reflow("All type variables in "),
alloc.reflow(match kind {
AliasKind::Structural => "type alias",
AliasKind::Opaque => "opaque type",
}),
alloc.reflow(" definitions must be named, and also declared before the "),
alloc.keyword(match kind { alloc.keyword(match kind {
AliasKind::Structural => ":", AliasKind::Structural => ":",
AliasKind::Opaque => ":=", AliasKind::Opaque => ":=",
}), }),
alloc.reflow(". Perhaps you intended to add a type parameter to this type?"), alloc.reflow(" symbol. Wildcard type variables ("),
alloc.keyword("*"),
alloc.reflow(") may not be used."),
]))); ])));
doc = alloc.stack(stack); doc = alloc.stack(stack);
title = UNBOUND_TYPE_VARIABLE.to_string(); title = WILDCARD_NOT_ALLOWED.to_string();
}
Problem::UnderscoreNotAllowed {
typ: alias,
num_underscores,
one_occurrence,
kind,
} => {
let mut stack = Vec::with_capacity(4);
if num_underscores == 1 {
stack.push(alloc.concat([
alloc.reflow("The definition of "),
alloc.symbol_unqualified(alias),
alloc.reflow(" includes an inferred ("),
alloc.keyword("_"),
alloc.reflow(") type:"),
]));
} else {
stack.push(alloc.concat([
alloc.reflow("The definition of "),
alloc.symbol_unqualified(alias),
alloc.reflow(" includes "),
text!(alloc, "{}", num_underscores),
alloc.reflow(" inferred ("),
alloc.keyword("_"),
alloc.reflow(") types:"),
]));
stack.push(alloc.reflow("Here is one of them:"));
}
stack.push(alloc.region(lines.convert_region(one_occurrence)));
stack.push(alloc.tip().append(alloc.concat([
alloc.reflow(match kind {
AliasKind::Structural => "Type alias",
AliasKind::Opaque => "Opaque type",
}),
alloc.reflow(" definitions may not use inferred types ("),
alloc.keyword("_"),
alloc.reflow(")."),
])));
doc = alloc.stack(stack);
title = UNDERSCORE_NOT_ALLOWED.to_string();
}
Problem::UndeclaredTypeVar {
typ: alias,
num_unbound,
one_occurrence,
kind,
} => {
let decl_symbol = match kind {
AliasKind::Structural => ":",
AliasKind::Opaque => ":=",
};
let mut stack = Vec::with_capacity(4);
if num_unbound == 1 {
stack.push(alloc.concat([
alloc.reflow("The definition of "),
alloc.symbol_unqualified(alias),
alloc.reflow(" includes an undeclared type variable:"),
]));
} else {
stack.push(alloc.concat([
alloc.reflow("The definition of "),
alloc.symbol_unqualified(alias),
alloc.reflow(" includes "),
text!(alloc, "{}", num_unbound),
alloc.reflow(" undeclared type variables."),
]));
stack.push(alloc.reflow("Here is one of them:"));
}
stack.push(alloc.region(lines.convert_region(one_occurrence)));
stack.push(alloc.tip().append(alloc.concat([
alloc.reflow("All type variables in "),
alloc.reflow(match kind {
AliasKind::Structural => "type alias",
AliasKind::Opaque => "opaque type",
}),
alloc.reflow(" definitions must be declared before the "),
alloc.keyword(decl_symbol),
alloc.reflow(" symbol."),
alloc.tip().append(alloc.stack(
[
alloc.reflow(
"You can declare type variables by putting them right before the ",
),
alloc.keyword(decl_symbol),
alloc.reflow(" symbol, separated by spaces."),
],
)),
])));
doc = alloc.stack(stack);
title = UNDECLARED_TYPE_VARIABLE.to_string();
} }
Problem::BadRecursion(entries) => { Problem::BadRecursion(entries) => {
doc = to_circular_def_doc(alloc, lines, &entries); doc = to_circular_def_doc(alloc, lines, &entries);