mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-26 13:29:12 +00:00
Revise errors for undeclared type vars in aliases
This commit is contained in:
parent
e5ea6dc461
commit
8357bd8c91
4 changed files with 207 additions and 39 deletions
|
@ -439,36 +439,54 @@ 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(());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(create_alias(
|
if let Some(loc_var) = inferred.first() {
|
||||||
symbol,
|
env.problems.push(Problem::UnderscoreNotAllowed {
|
||||||
name.region,
|
typ: symbol,
|
||||||
can_vars.clone(),
|
num_underscores: inferred.len(),
|
||||||
infer_ext_in_output,
|
one_occurrence: loc_var.region,
|
||||||
can_ann.typ,
|
kind,
|
||||||
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(
|
||||||
|
symbol,
|
||||||
|
name.region,
|
||||||
|
can_vars.clone(),
|
||||||
|
infer_ext_in_output,
|
||||||
|
can_ann.typ,
|
||||||
|
kind,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`.
|
/// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`.
|
||||||
|
|
|
@ -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!(
|
||||||
|
|
|
@ -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,
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue