record, tag, alias errors

- duplicate fields and tags are reported
- circular aliases are reported
This commit is contained in:
Folkert 2020-04-13 20:53:16 +02:00
parent 0372b34e86
commit f6af66f342
13 changed files with 679 additions and 55 deletions

View file

@ -326,6 +326,8 @@ fn can_annotation_help(
TagUnion { tags, ext } => { TagUnion { tags, ext } => {
let mut tag_types = Vec::with_capacity(tags.len()); let mut tag_types = Vec::with_capacity(tags.len());
let mut seen = MutSet::default();
for tag in tags.iter() { for tag in tags.iter() {
can_tag( can_tag(
env, env,
@ -338,6 +340,17 @@ fn can_annotation_help(
&mut tag_types, &mut tag_types,
references, references,
); );
let added = &tag_types.last().unwrap().0;
let is_new = seen.insert(added.clone());
if !is_new {
env.problem(roc_problem::can::Problem::DuplicateTag {
tag_name: added.clone(),
tag_region: tag.region,
tag_union_region: region,
});
}
} }
let ext_type = match ext { let ext_type = match ext {
@ -405,7 +418,16 @@ fn can_assigned_field<'a>(
); );
let label = Lowercase::from(field_name.value); let label = Lowercase::from(field_name.value);
field_types.insert(label, field_type); let removed = field_types.insert(label, field_type);
if removed.is_some() {
let field_region = Region::span_across(&field_name.region, &annotation.region);
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
field_name: field_name.value.into(),
field_region,
record_region: region,
});
}
} }
LabelOnly(loc_field_name) => { LabelOnly(loc_field_name) => {
// Interpret { a, b } as { a : a, b : b } // Interpret { a, b } as { a : a, b : b }

View file

@ -32,24 +32,32 @@ impl Constraint {
match self { match self {
True | SaveTheEnvironment => {} True | SaveTheEnvironment => {}
Eq(typ, expected, _, _) => { Eq(typ, expected, _, region) => {
expected let expected_region = expected.get_annotation_region().unwrap_or(*region);
.get_type_mut_ref() expected.get_type_mut_ref().instantiate_aliases(
.instantiate_aliases(aliases, var_store, introduced); expected_region,
typ.instantiate_aliases(aliases, var_store, introduced); aliases,
var_store,
introduced,
);
typ.instantiate_aliases(*region, aliases, var_store, introduced);
} }
Lookup(_, expected, _) => { Lookup(_, expected, region) => {
expected let expected_region = expected.get_annotation_region().unwrap_or(*region);
.get_type_mut_ref() expected.get_type_mut_ref().instantiate_aliases(
.instantiate_aliases(aliases, var_store, introduced); expected_region,
aliases,
var_store,
introduced,
);
} }
Pattern(_, _, typ, pexpected) => { Pattern(region, _, typ, pexpected) => {
pexpected pexpected
.get_type_mut_ref() .get_type_mut_ref()
.instantiate_aliases(aliases, var_store, introduced); .instantiate_aliases(*region, aliases, var_store, introduced);
typ.instantiate_aliases(aliases, var_store, introduced); typ.instantiate_aliases(*region, aliases, var_store, introduced);
} }
And(nested) => { And(nested) => {
@ -65,8 +73,8 @@ impl Constraint {
} }
let mut introduced = ImSet::default(); let mut introduced = ImSet::default();
for Located { value: typ, .. } in letcon.def_types.iter_mut() { for Located { region, value: typ } in letcon.def_types.iter_mut() {
typ.instantiate_aliases(&new_aliases, var_store, &mut introduced); typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced);
} }
letcon.defs_constraint.instantiate_aliases_help( letcon.defs_constraint.instantiate_aliases_help(

View file

@ -201,6 +201,7 @@ pub fn canonicalize_defs<'a>(
let mut can_vars: Vec<Located<(Lowercase, Variable)>> = let mut can_vars: Vec<Located<(Lowercase, Variable)>> =
Vec::with_capacity(vars.len()); Vec::with_capacity(vars.len());
let mut is_phantom = false;
for loc_lowercase in vars { for loc_lowercase in vars {
if let Some(var) = can_ann if let Some(var) = can_ann
.introduced_variables .introduced_variables
@ -212,12 +213,31 @@ pub fn canonicalize_defs<'a>(
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
} else { } else {
panic!("TODO handle phantom type variables, they are not allowed!\nThe {:?} variable in the definition of {:?} gives trouble", loc_lowercase, symbol); is_phantom = true;
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
} }
} }
if is_phantom {
// Bail out
continue;
}
if can_ann.typ.contains_symbol(symbol) { if can_ann.typ.contains_symbol(symbol) {
make_tag_union_recursive(symbol, &mut can_ann.typ, var_store); make_tag_union_recursive(
env,
symbol,
name.region,
vec![],
&mut can_ann.typ,
var_store,
&mut false,
);
} }
let alias = roc_types::types::Alias { let alias = roc_types::types::Alias {
@ -231,7 +251,7 @@ pub fn canonicalize_defs<'a>(
} }
} }
correct_mutual_recursive_type_alias(&mut aliases, &var_store); correct_mutual_recursive_type_alias(env, &mut aliases, &var_store);
// Now that we have the scope completely assembled, and shadowing resolved, // Now that we have the scope completely assembled, and shadowing resolved,
// we're ready to canonicalize any body exprs. // we're ready to canonicalize any body exprs.
@ -836,7 +856,11 @@ fn canonicalize_pending_def<'a>(
region: loc_lowercase.region, region: loc_lowercase.region,
}); });
} else { } else {
panic!("TODO handle phantom type variables, they are not allowed!"); env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
} }
} }
@ -852,7 +876,9 @@ fn canonicalize_pending_def<'a>(
scope.add_alias(symbol, name.region, can_vars, rec_type_union); scope.add_alias(symbol, name.region, can_vars, rec_type_union);
} else { } else {
panic!("recursion in type alias that is not behind a Tag"); env.problems
.push(Problem::CyclicAlias(symbol, name.region, vec![]));
return output;
} }
} }
@ -1385,7 +1411,11 @@ fn pending_typed_body<'a>(
} }
/// Make aliases recursive /// Make aliases recursive
fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var_store: &VarStore) { fn correct_mutual_recursive_type_alias<'a>(
env: &mut Env<'a>,
aliases: &mut SendMap<Symbol, Alias>,
var_store: &VarStore,
) {
let mut symbols_introduced = ImSet::default(); let mut symbols_introduced = ImSet::default();
for (key, _) in aliases.iter() { for (key, _) in aliases.iter() {
@ -1434,11 +1464,17 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
&mutually_recursive_symbols, &mutually_recursive_symbols,
all_successors_without_self, all_successors_without_self,
) { ) {
// make sure we report only one error for the cycle, not an error for every
// alias in the cycle.
let mut can_still_report_error = true;
// TODO use itertools to be more efficient here // TODO use itertools to be more efficient here
for rec in &cycle { for rec in &cycle {
let mut to_instantiate = ImMap::default(); let mut to_instantiate = ImMap::default();
let mut others = Vec::with_capacity(cycle.len() - 1);
for other in &cycle { for other in &cycle {
if rec != other { if rec != other {
others.push(*other);
if let Some(alias) = originals.get(other) { if let Some(alias) = originals.get(other) {
to_instantiate.insert(*other, alias.clone()); to_instantiate.insert(*other, alias.clone());
} }
@ -1447,11 +1483,20 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
if let Some(alias) = aliases.get_mut(rec) { if let Some(alias) = aliases.get_mut(rec) {
alias.typ.instantiate_aliases( alias.typ.instantiate_aliases(
alias.region,
&to_instantiate, &to_instantiate,
var_store, var_store,
&mut ImSet::default(), &mut ImSet::default(),
); );
make_tag_union_recursive(*rec, &mut alias.typ, var_store); make_tag_union_recursive(
env,
*rec,
alias.region,
others,
&mut alias.typ,
var_store,
&mut can_still_report_error,
);
} }
} }
} }
@ -1459,14 +1504,40 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap<Symbol, Alias>, var
} }
} }
fn make_tag_union_recursive(symbol: Symbol, typ: &mut Type, var_store: &VarStore) { fn make_tag_union_recursive<'a>(
env: &mut Env<'a>,
symbol: Symbol,
region: Region,
others: Vec<Symbol>,
typ: &mut Type,
var_store: &VarStore,
can_report_error: &mut bool,
) {
match typ { match typ {
Type::TagUnion(tags, ext) => { Type::TagUnion(tags, ext) => {
let rec_var = var_store.fresh(); let rec_var = var_store.fresh();
*typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); *typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
typ.substitute_alias(symbol, &Type::Variable(rec_var)); typ.substitute_alias(symbol, &Type::Variable(rec_var));
} }
Type::Alias(_, _, actual) => make_tag_union_recursive(symbol, actual, var_store), Type::Alias(_, _, actual) => make_tag_union_recursive(
_ => panic!("recursion in type alias is not behind a Tag"), env,
symbol,
region,
others,
actual,
var_store,
can_report_error,
),
_ => {
let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone());
*typ = Type::Erroneous(problem);
if *can_report_error {
*can_report_error = false;
let problem = Problem::CyclicAlias(symbol, region, others);
env.problems.push(problem);
}
}
} }
} }

View file

@ -71,6 +71,15 @@ impl<T> Expected<T> {
} }
} }
pub fn get_annotation_region(&self) -> Option<Region> {
match self {
Expected::FromAnnotation(_, _, AnnotationSource::TypedBody { region }, _) => {
Some(*region)
}
_ => None,
}
}
pub fn replace<U>(self, new: U) -> Expected<U> { pub fn replace<U>(self, new: U) -> Expected<U> {
match self { match self {
Expected::NoExpectation(_val) => Expected::NoExpectation(new), Expected::NoExpectation(_val) => Expected::NoExpectation(new),

View file

@ -193,7 +193,8 @@ pub fn canonicalize_expr<'a>(
let (can_update, update_out) = let (can_update, update_out) =
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value); canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
if let Var(symbol) = &can_update.value { if let Var(symbol) = &can_update.value {
let (can_fields, mut output) = canonicalize_fields(env, var_store, scope, fields); let (can_fields, mut output) =
canonicalize_fields(env, var_store, scope, region, fields);
output.references = output.references.union(update_out.references); output.references = output.references.union(update_out.references);
@ -219,7 +220,8 @@ pub fn canonicalize_expr<'a>(
if fields.is_empty() { if fields.is_empty() {
(EmptyRecord, Output::default()) (EmptyRecord, Output::default())
} else { } else {
let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields); let (can_fields, output) =
canonicalize_fields(env, var_store, scope, region, fields);
( (
Record { Record {
@ -885,6 +887,7 @@ fn canonicalize_fields<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
var_store: &VarStore, var_store: &VarStore,
scope: &mut Scope, scope: &mut Scope,
region: Region,
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>], fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
) -> (SendMap<Lowercase, Field>, Output) { ) -> (SendMap<Lowercase, Field>, Output) {
let mut can_fields = SendMap::default(); let mut can_fields = SendMap::default();
@ -900,7 +903,15 @@ fn canonicalize_fields<'a>(
loc_expr: Box::new(field_expr), loc_expr: Box::new(field_expr),
}; };
can_fields.insert(label, field); let replaced = can_fields.insert(label.clone(), field);
if replaced.is_some() {
env.problems.push(Problem::DuplicateRecordFieldValue {
field_name: label,
field_region: loc_field.region,
record_region: region,
});
}
output.references = output.references.union(field_out.references); output.references = output.references.union(field_out.references);
} }

View file

@ -1,6 +1,6 @@
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc_collections::all::MutSet; use roc_collections::all::MutSet;
use roc_module::ident::Ident; use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_parse::operator::BinOp; use roc_parse::operator::BinOp;
use roc_parse::pattern::PatternType; use roc_parse::pattern::PatternType;
@ -21,6 +21,27 @@ pub enum Problem {
original_region: Region, original_region: Region,
shadow: Located<Ident>, shadow: Located<Ident>,
}, },
CyclicAlias(Symbol, Region, Vec<Symbol>),
PhantomTypeArgument {
alias: Symbol,
variable_region: Region,
variable_name: Lowercase,
},
DuplicateRecordFieldValue {
field_name: Lowercase,
record_region: Region,
field_region: Region,
},
DuplicateRecordFieldType {
field_name: Lowercase,
record_region: Region,
field_region: Region,
},
DuplicateTag {
tag_name: TagName,
tag_union_region: Region,
tag_region: Region,
},
RuntimeError(RuntimeError), RuntimeError(RuntimeError),
} }

View file

@ -401,6 +401,91 @@ pub fn can_problem<'b>(
shadow, shadow,
}, },
), ),
Problem::CyclicAlias(symbol, region, others) => {
if others.is_empty() {
todo!("cyclic alias")
} else {
alloc.stack(vec![
alloc
.reflow("The ")
.append(alloc.symbol_unqualified(symbol))
.append(alloc.reflow(" alias is recursive in an invalid way:")),
alloc.region(region),
alloc
.reflow("The ")
.append(alloc.symbol_unqualified(symbol))
.append(alloc.reflow(
" alias depends on itself through the following chain of definitions:",
)),
cycle(
alloc,
4,
alloc.symbol_unqualified(symbol),
others
.into_iter()
.map(|other| alloc.symbol_unqualified(other))
.collect::<Vec<_>>(),
),
alloc.reflow(
"Recursion in aliases is only allowed if recursion happens behind a tag.",
),
])
}
}
Problem::PhantomTypeArgument {
alias,
variable_region,
variable_name,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.type_variable(variable_name),
alloc.reflow(" type variable is not used in the "),
alloc.symbol_unqualified(alias),
alloc.reflow(" alias definition:"),
]),
alloc.region(variable_region),
alloc.reflow("Roc does not allow phantom type parameters!"),
]),
Problem::DuplicateRecordFieldValue {
field_name,
field_region,
record_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This record defines the "),
alloc.record_field(field_name),
alloc.reflow(" field twice!"),
]),
alloc.region_with_subregion(record_region, field_region),
alloc.reflow("In the rest of the program, I will use the second definition."),
]),
Problem::DuplicateRecordFieldType {
field_name,
field_region,
record_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This annotation defines the "),
alloc.record_field(field_name),
alloc.reflow(" field twice!"),
]),
alloc.region_with_subregion(record_region, field_region),
alloc.reflow("In the rest of the program, I will use the second definition."),
]),
Problem::DuplicateTag {
tag_name,
tag_union_region,
tag_region,
} => alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("This annotation defines the "),
alloc.tag_name(tag_name),
alloc.reflow(" tag twice!"),
]),
alloc.region_with_subregion(tag_union_region, tag_region),
alloc.reflow("In the rest of the program, I will use the second definition."),
]),
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error), Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
}; };
@ -711,6 +796,7 @@ impl<'a> RocDocAllocator<'a> {
self.text(content.to_string()).annotate(Annotation::BinOp) self.text(content.to_string()).annotate(Annotation::BinOp)
} }
/// Turns of backticks/colors in a block
pub fn type_block( pub fn type_block(
&'a self, &'a self,
content: DocBuilder<'a, Self, Annotation>, content: DocBuilder<'a, Self, Annotation>,
@ -731,6 +817,11 @@ impl<'a> RocDocAllocator<'a> {
) -> DocBuilder<'a, Self, Annotation> { ) -> DocBuilder<'a, Self, Annotation> {
debug_assert!(region.contains(&sub_region)); debug_assert!(region.contains(&sub_region));
// If the outer region takes more than 1 full screen (~60 lines), only show the inner region
if region.end_line - region.start_line > 60 {
return self.region_with_subregion(sub_region, sub_region);
}
// if true, the final line of the snippet will be some ^^^ that point to the region where // if true, the final line of the snippet will be some ^^^ that point to the region where
// the problem is. Otherwise, the snippet will have a > on the lines that are in the regon // the problem is. Otherwise, the snippet will have a > on the lines that are in the regon
// where the problem is. // where the problem is.

View file

@ -29,6 +29,54 @@ pub fn type_problem<'b>(
CircularType(region, symbol, overall_type) => { CircularType(region, symbol, overall_type) => {
to_circular_report(alloc, filename, region, symbol, overall_type) to_circular_report(alloc, filename, region, symbol, overall_type)
} }
BadType(type_problem) => {
use roc_types::types::Problem::*;
match type_problem {
BadTypeArguments {
symbol,
region,
type_got,
alias_needs,
} => {
let needed_arguments = if alias_needs == 1 {
alloc.reflow("1 type argument")
} else {
alloc
.text(alias_needs.to_string())
.append(alloc.reflow(" type arguments"))
};
let found_arguments = alloc.text(type_got.to_string());
let doc = alloc.stack(vec![
alloc.concat(vec![
alloc.reflow("The "),
alloc.symbol_unqualified(symbol),
alloc.reflow(" alias expects "),
needed_arguments,
alloc.reflow(", but it got "),
found_arguments,
alloc.reflow(" instead:"),
]),
alloc.region(region),
alloc.reflow("Are there missing parentheses?"),
]);
let title = if type_got > alias_needs {
"TOO MANY TYPE ARGUMENTS".to_string()
} else {
"TOO FEW TYPE ARGUMENTS".to_string()
};
Report {
filename,
title,
doc,
}
}
other => panic!("unhandled bad type: {:?}", other),
}
}
} }
} }

View file

@ -2257,4 +2257,286 @@ mod test_reporting {
), ),
) )
} }
#[test]
fn circular_alias() {
report_problem_as(
indoc!(
r#"
Foo : { x: Bar }
Bar : { y : Foo }
f : Foo
f
"#
),
// should not report Bar as unused!
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
The `Bar` alias is recursive in an invalid way:
2 Bar : { y : Foo }
^^^^^^^^^^^
The `Bar` alias depends on itself through the following chain of
definitions:
Bar
Foo
Recursion in aliases is only allowed if recursion happens behind a
tag.
-- SYNTAX PROBLEM --------------------------------------------------------------
`Bar` is not used anywhere in your code.
2 Bar : { y : Foo }
^^^^^^^^^^^^^^^^^
If you didn't intend on using `Bar` then remove it so future readers of
your code don't wonder why it is there.
"#
),
)
}
#[test]
fn record_duplicate_field_same_type() {
report_problem_as(
indoc!(
r#"
{ x: 4, y: 3, x: 4 }
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This record defines the `.x` field twice!
1 { x: 4, y: 3, x: 4 }
^^^^
In the rest of the program, I will use the second definition.
"#
),
)
}
#[test]
fn record_duplicate_field_different_types() {
report_problem_as(
indoc!(
r#"
{ x: 4, y: 3, x: "foo" }
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This record defines the `.x` field twice!
1 { x: 4, y: 3, x: "foo" }
^^^^^^^^
In the rest of the program, I will use the second definition.
"#
),
)
}
#[test]
fn record_type_duplicate_field() {
report_problem_as(
indoc!(
r#"
a : { foo : Int, bar : Float, foo : Str }
a = { bar: 3.0, foo: "foo" }
a
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This annotation defines the `.foo` field twice!
1 a : { foo : Int, bar : Float, foo : Str }
^^^^^^^^^
In the rest of the program, I will use the second definition.
"#
),
)
}
#[test]
fn tag_union_duplicate_tag() {
report_problem_as(
indoc!(
r#"
a : [ Foo Int, Bar Float, Foo Str ]
a = Foo "foo"
a
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This annotation defines the `Foo` tag twice!
1 a : [ Foo Int, Bar Float, Foo Str ]
^^^^^^^
In the rest of the program, I will use the second definition.
"#
),
)
}
#[test]
fn invalid_num() {
report_problem_as(
indoc!(
r#"
a : Num Int Float
a = 3
a
"#
),
indoc!(
r#"
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
The `Num` alias expects 1 type argument, but it got 2 instead:
1 a : Num Int Float
^^^^^^^^^^^^^
Are there missing parentheses?
"#
),
)
}
#[test]
fn invalid_num_fn() {
report_problem_as(
indoc!(
r#"
f : Bool -> Num Int Float
f = \_ -> 3
f
"#
),
indoc!(
r#"
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
The `Num` alias expects 1 type argument, but it got 2 instead:
1 f : Bool -> Num Int Float
^^^^^^^^^^^^^
Are there missing parentheses?
"#
),
)
}
#[test]
fn too_few_type_arguments() {
report_problem_as(
indoc!(
r#"
Pair a b : [ Pair a b ]
x : Pair Int
x = 3
x
"#
),
indoc!(
r#"
-- TOO FEW TYPE ARGUMENTS ------------------------------------------------------
The `Pair` alias expects 2 type arguments, but it got 1 instead:
3 x : Pair Int
^^^^^^^^
Are there missing parentheses?
"#
),
)
}
#[test]
fn too_many_type_arguments() {
report_problem_as(
indoc!(
r#"
Pair a b : [ Pair a b ]
x : Pair Int Int Int
x = 3
x
"#
),
indoc!(
r#"
-- TOO MANY TYPE ARGUMENTS -----------------------------------------------------
The `Pair` alias expects 2 type arguments, but it got 3 instead:
3 x : Pair Int Int Int
^^^^^^^^^^^^^^^^
Are there missing parentheses?
"#
),
)
}
#[test]
fn phantom_type_variable() {
report_problem_as(
indoc!(
r#"
Foo a : [ Foo ]
f : Foo Int
f
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
The `a` type variable is not used in the `Foo` alias definition:
1 Foo a : [ Foo ]
^
Roc does not allow phantom type parameters!
"#
),
)
}
} }

View file

@ -21,6 +21,7 @@ pub enum TypeError {
BadExpr(Region, Category, ErrorType, Expected<ErrorType>), BadExpr(Region, Category, ErrorType, Expected<ErrorType>),
BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>), BadPattern(Region, PatternCategory, ErrorType, PExpected<ErrorType>),
CircularType(Region, Symbol, ErrorType), CircularType(Region, Symbol, ErrorType),
BadType(roc_types::types::Problem),
} }
pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>; pub type SubsByModule = MutMap<ModuleId, ExposedModuleTypes>;
@ -166,6 +167,13 @@ fn solve(
problems.push(problem); problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state state
} }
} }
@ -228,6 +236,13 @@ fn solve(
problems.push(problem); problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state state
} }
} }
@ -278,6 +293,13 @@ fn solve(
problems.push(problem); problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state state
} }
} }
@ -829,7 +851,7 @@ fn circular_error(
loc_var: &Located<Variable>, loc_var: &Located<Variable>,
) { ) {
let var = loc_var.value; let var = loc_var.value;
let error_type = subs.var_to_error_type(var); let (error_type, _) = subs.var_to_error_type(var);
let problem = TypeError::CircularType(loc_var.region, symbol, error_type); let problem = TypeError::CircularType(loc_var.region, symbol, error_type);
subs.set_content(var, Content::Error); subs.set_content(var, Content::Error);

View file

@ -37,9 +37,10 @@ impl fmt::Debug for Mark {
} }
#[derive(Default)] #[derive(Default)]
struct NameState { struct ErrorTypeState {
taken: MutSet<Lowercase>, taken: MutSet<Lowercase>,
normals: u32, normals: u32,
problems: Vec<crate::types::Problem>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
@ -363,7 +364,7 @@ impl Subs {
explicit_substitute(self, x, y, z, &mut seen) explicit_substitute(self, x, y, z, &mut seen)
} }
pub fn var_to_error_type(&mut self, var: Variable) -> ErrorType { pub fn var_to_error_type(&mut self, var: Variable) -> (ErrorType, Vec<crate::types::Problem>) {
let names = get_var_names(self, var, ImMap::default()); let names = get_var_names(self, var, ImMap::default());
let mut taken = MutSet::default(); let mut taken = MutSet::default();
@ -371,9 +372,13 @@ impl Subs {
taken.insert(name); taken.insert(name);
} }
let mut state = NameState { taken, normals: 0 }; let mut state = ErrorTypeState {
taken,
normals: 0,
problems: Vec::new(),
};
var_to_err_type(self, &mut state, var) (var_to_err_type(self, &mut state, var), state.problems)
} }
pub fn restore(&mut self, var: Variable) { pub fn restore(&mut self, var: Variable) {
@ -1114,7 +1119,7 @@ where
} }
} }
fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> ErrorType { fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType {
let desc = subs.get(var); let desc = subs.get(var);
if desc.mark == Mark::OCCURS { if desc.mark == Mark::OCCURS {
@ -1132,7 +1137,7 @@ fn var_to_err_type(subs: &mut Subs, state: &mut NameState, var: Variable) -> Err
fn content_to_err_type( fn content_to_err_type(
subs: &mut Subs, subs: &mut Subs,
state: &mut NameState, state: &mut ErrorTypeState,
var: Variable, var: Variable,
content: Content, content: Content,
) -> ErrorType { ) -> ErrorType {
@ -1174,7 +1179,11 @@ fn content_to_err_type(
} }
} }
fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: FlatType) -> ErrorType { fn flat_type_to_err_type(
subs: &mut Subs,
state: &mut ErrorTypeState,
flat_type: FlatType,
) -> ErrorType {
use self::FlatType::*; use self::FlatType::*;
match flat_type { match flat_type {
@ -1296,11 +1305,15 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
Boolean(b) => ErrorType::Boolean(b), Boolean(b) => ErrorType::Boolean(b),
Erroneous(_) => ErrorType::Error, Erroneous(problem) => {
state.problems.push(problem);
ErrorType::Error
}
} }
} }
fn get_fresh_var_name(state: &mut NameState) -> Lowercase { fn get_fresh_var_name(state: &mut ErrorTypeState) -> Lowercase {
let (name, new_index) = name_type_var(state.normals, &mut state.taken); let (name, new_index) = name_type_var(state.normals, &mut state.taken);
state.normals = new_index; state.normals = new_index;

View file

@ -395,6 +395,7 @@ impl Type {
pub fn instantiate_aliases( pub fn instantiate_aliases(
&mut self, &mut self,
region: Region,
aliases: &ImMap<Symbol, Alias>, aliases: &ImMap<Symbol, Alias>,
var_store: &VarStore, var_store: &VarStore,
introduced: &mut ImSet<Variable>, introduced: &mut ImSet<Variable>,
@ -404,34 +405,44 @@ impl Type {
match self { match self {
Function(args, ret) => { Function(args, ret) => {
for arg in args { for arg in args {
arg.instantiate_aliases(aliases, var_store, introduced); arg.instantiate_aliases(region, aliases, var_store, introduced);
} }
ret.instantiate_aliases(aliases, var_store, introduced); ret.instantiate_aliases(region, aliases, var_store, introduced);
} }
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags { for (_, args) in tags {
for x in args { for x in args {
x.instantiate_aliases(aliases, var_store, introduced); x.instantiate_aliases(region, aliases, var_store, introduced);
} }
} }
ext.instantiate_aliases(aliases, var_store, introduced); ext.instantiate_aliases(region, aliases, var_store, introduced);
} }
Record(fields, ext) => { Record(fields, ext) => {
for x in fields.iter_mut() { for x in fields.iter_mut() {
x.instantiate_aliases(aliases, var_store, introduced); x.instantiate_aliases(region, aliases, var_store, introduced);
} }
ext.instantiate_aliases(aliases, var_store, introduced); ext.instantiate_aliases(region, aliases, var_store, introduced);
} }
Alias(_, type_args, actual_type) => { Alias(_, type_args, actual_type) => {
for arg in type_args { for arg in type_args {
arg.1.instantiate_aliases(aliases, var_store, introduced); arg.1
.instantiate_aliases(region, aliases, var_store, introduced);
} }
actual_type.instantiate_aliases(aliases, var_store, introduced); actual_type.instantiate_aliases(region, aliases, var_store, introduced);
} }
Apply(symbol, args) => { Apply(symbol, args) => {
if let Some(alias) = aliases.get(symbol) { if let Some(alias) = aliases.get(symbol) {
debug_assert!(args.len() == alias.vars.len()); if args.len() != alias.vars.len() {
*self = Type::Erroneous(Problem::BadTypeArguments {
symbol: *symbol,
region,
type_got: args.len() as u8,
alias_needs: alias.vars.len() as u8,
});
return;
}
let mut actual = alias.typ.clone(); let mut actual = alias.typ.clone();
let mut named_args = Vec::with_capacity(args.len()); let mut named_args = Vec::with_capacity(args.len());
@ -447,7 +458,7 @@ impl Type {
) in alias.vars.iter().zip(args.iter()) ) in alias.vars.iter().zip(args.iter())
{ {
let mut filler = filler.clone(); let mut filler = filler.clone();
filler.instantiate_aliases(aliases, var_store, introduced); filler.instantiate_aliases(region, aliases, var_store, introduced);
named_args.push((lowercase.clone(), filler.clone())); named_args.push((lowercase.clone(), filler.clone()));
substitution.insert(*placeholder, filler); substitution.insert(*placeholder, filler);
} }
@ -463,7 +474,7 @@ impl Type {
} }
actual.substitute(&substitution); actual.substitute(&substitution);
actual.instantiate_aliases(aliases, var_store, introduced); actual.instantiate_aliases(region, aliases, var_store, introduced);
// instantiate recursion variable! // instantiate recursion variable!
if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual { if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual {
@ -487,7 +498,7 @@ impl Type {
} else { } else {
// one of the special-cased Apply types. // one of the special-cased Apply types.
for x in args { for x in args {
x.instantiate_aliases(aliases, var_store, introduced); x.instantiate_aliases(region, aliases, var_store, introduced);
} }
} }
} }
@ -691,11 +702,18 @@ pub struct Alias {
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum Problem { pub enum Problem {
CanonicalizationProblem, CanonicalizationProblem,
Mismatch(Mismatch, ErrorType, ErrorType),
CircularType(Symbol, ErrorType, Region), CircularType(Symbol, ErrorType, Region),
CyclicAlias(Symbol, Region, Vec<Symbol>),
UnrecognizedIdent(InlinableString), UnrecognizedIdent(InlinableString),
Shadowed(Region, Located<Ident>), Shadowed(Region, Located<Ident>),
BadTypeArguments {
symbol: Symbol,
region: Region,
type_got: u8,
alias_needs: u8,
},
InvalidModule, InvalidModule,
// Mismatch(Mismatch, ErrorType, ErrorType),
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Debug, Clone)]

View file

@ -73,6 +73,7 @@ struct Context {
pub enum Unified { pub enum Unified {
Success(Pool), Success(Pool),
Failure(Pool, ErrorType, ErrorType), Failure(Pool, ErrorType, ErrorType),
BadType(Pool, roc_types::types::Problem),
} }
#[derive(Debug)] #[derive(Debug)]
@ -91,13 +92,20 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified {
if mismatches.is_empty() { if mismatches.is_empty() {
Unified::Success(vars) Unified::Success(vars)
} else { } else {
let type1 = subs.var_to_error_type(var1); let (type1, mut problems) = subs.var_to_error_type(var1);
let type2 = subs.var_to_error_type(var2); let (type2, problems2) = subs.var_to_error_type(var2);
problems.extend(problems2);
subs.union(var1, var2, Content::Error.into()); subs.union(var1, var2, Content::Error.into());
if !problems.is_empty() {
Unified::BadType(vars, problems.remove(0))
} else {
Unified::Failure(vars, type1, type2) Unified::Failure(vars, type1, type2)
} }
} }
}
#[inline(always)] #[inline(always)]
pub fn unify_pool(subs: &mut Subs, pool: &mut Pool, var1: Variable, var2: Variable) -> Outcome { pub fn unify_pool(subs: &mut Subs, pool: &mut Pool, var1: Variable, var2: Variable) -> Outcome {