diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index c70e90fcd6..f95e184e71 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -326,6 +326,8 @@ fn can_annotation_help( TagUnion { tags, ext } => { let mut tag_types = Vec::with_capacity(tags.len()); + let mut seen = MutSet::default(); + for tag in tags.iter() { can_tag( env, @@ -338,6 +340,17 @@ fn can_annotation_help( &mut tag_types, 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 { @@ -405,7 +418,16 @@ fn can_assigned_field<'a>( ); 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) => { // Interpret { a, b } as { a : a, b : b } diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index 1d39fa5aa6..0d029c3b96 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -32,24 +32,32 @@ impl Constraint { match self { True | SaveTheEnvironment => {} - Eq(typ, expected, _, _) => { - expected - .get_type_mut_ref() - .instantiate_aliases(aliases, var_store, introduced); - typ.instantiate_aliases(aliases, var_store, introduced); + Eq(typ, expected, _, region) => { + let expected_region = expected.get_annotation_region().unwrap_or(*region); + expected.get_type_mut_ref().instantiate_aliases( + expected_region, + aliases, + var_store, + introduced, + ); + typ.instantiate_aliases(*region, aliases, var_store, introduced); } - Lookup(_, expected, _) => { - expected - .get_type_mut_ref() - .instantiate_aliases(aliases, var_store, introduced); + Lookup(_, expected, region) => { + let expected_region = expected.get_annotation_region().unwrap_or(*region); + expected.get_type_mut_ref().instantiate_aliases( + expected_region, + aliases, + var_store, + introduced, + ); } - Pattern(_, _, typ, pexpected) => { + Pattern(region, _, typ, pexpected) => { pexpected .get_type_mut_ref() - .instantiate_aliases(aliases, var_store, introduced); - typ.instantiate_aliases(aliases, var_store, introduced); + .instantiate_aliases(*region, aliases, var_store, introduced); + typ.instantiate_aliases(*region, aliases, var_store, introduced); } And(nested) => { @@ -65,8 +73,8 @@ impl Constraint { } let mut introduced = ImSet::default(); - for Located { value: typ, .. } in letcon.def_types.iter_mut() { - typ.instantiate_aliases(&new_aliases, var_store, &mut introduced); + for Located { region, value: typ } in letcon.def_types.iter_mut() { + typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced); } letcon.defs_constraint.instantiate_aliases_help( diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 0caf691663..b19c5fea44 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -201,6 +201,7 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); + let mut is_phantom = false; for loc_lowercase in vars { if let Some(var) = can_ann .introduced_variables @@ -212,12 +213,31 @@ pub fn canonicalize_defs<'a>( region: loc_lowercase.region, }); } 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) { - 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 { @@ -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, // we're ready to canonicalize any body exprs. @@ -836,7 +856,11 @@ fn canonicalize_pending_def<'a>( region: loc_lowercase.region, }); } 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); } 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 -fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var_store: &VarStore) { +fn correct_mutual_recursive_type_alias<'a>( + env: &mut Env<'a>, + aliases: &mut SendMap, + var_store: &VarStore, +) { let mut symbols_introduced = ImSet::default(); for (key, _) in aliases.iter() { @@ -1434,11 +1464,17 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var &mutually_recursive_symbols, 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 for rec in &cycle { let mut to_instantiate = ImMap::default(); + let mut others = Vec::with_capacity(cycle.len() - 1); for other in &cycle { if rec != other { + others.push(*other); if let Some(alias) = originals.get(other) { to_instantiate.insert(*other, alias.clone()); } @@ -1447,11 +1483,20 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var if let Some(alias) = aliases.get_mut(rec) { alias.typ.instantiate_aliases( + alias.region, &to_instantiate, var_store, &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, 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, + typ: &mut Type, + var_store: &VarStore, + can_report_error: &mut bool, +) { match typ { Type::TagUnion(tags, ext) => { let rec_var = var_store.fresh(); *typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); typ.substitute_alias(symbol, &Type::Variable(rec_var)); } - Type::Alias(_, _, actual) => make_tag_union_recursive(symbol, actual, var_store), - _ => panic!("recursion in type alias is not behind a Tag"), + Type::Alias(_, _, actual) => make_tag_union_recursive( + 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); + } + } } } diff --git a/compiler/can/src/expected.rs b/compiler/can/src/expected.rs index b7ef500d9f..4075624dcc 100644 --- a/compiler/can/src/expected.rs +++ b/compiler/can/src/expected.rs @@ -71,6 +71,15 @@ impl Expected { } } + pub fn get_annotation_region(&self) -> Option { + match self { + Expected::FromAnnotation(_, _, AnnotationSource::TypedBody { region }, _) => { + Some(*region) + } + _ => None, + } + } + pub fn replace(self, new: U) -> Expected { match self { Expected::NoExpectation(_val) => Expected::NoExpectation(new), diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 6889281ee7..fceb769a6f 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -193,7 +193,8 @@ pub fn canonicalize_expr<'a>( let (can_update, update_out) = canonicalize_expr(env, var_store, scope, loc_update.region, &loc_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); @@ -219,7 +220,8 @@ pub fn canonicalize_expr<'a>( if fields.is_empty() { (EmptyRecord, Output::default()) } 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 { @@ -885,6 +887,7 @@ fn canonicalize_fields<'a>( env: &mut Env<'a>, var_store: &VarStore, scope: &mut Scope, + region: Region, fields: &'a [Located>>], ) -> (SendMap, Output) { let mut can_fields = SendMap::default(); @@ -900,7 +903,15 @@ fn canonicalize_fields<'a>( 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); } diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index a4c50ddd1a..02b8684abb 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -1,6 +1,6 @@ use inlinable_string::InlinableString; 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_parse::operator::BinOp; use roc_parse::pattern::PatternType; @@ -21,6 +21,27 @@ pub enum Problem { original_region: Region, shadow: Located, }, + CyclicAlias(Symbol, Region, Vec), + 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), } diff --git a/compiler/reporting/src/report.rs b/compiler/reporting/src/report.rs index 875bd6dd11..cc6d1d1927 100644 --- a/compiler/reporting/src/report.rs +++ b/compiler/reporting/src/report.rs @@ -401,6 +401,91 @@ pub fn can_problem<'b>( 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::>(), + ), + 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), }; @@ -711,6 +796,7 @@ impl<'a> RocDocAllocator<'a> { self.text(content.to_string()).annotate(Annotation::BinOp) } + /// Turns of backticks/colors in a block pub fn type_block( &'a self, content: DocBuilder<'a, Self, Annotation>, @@ -731,6 +817,11 @@ impl<'a> RocDocAllocator<'a> { ) -> DocBuilder<'a, Self, Annotation> { 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 // the problem is. Otherwise, the snippet will have a > on the lines that are in the regon // where the problem is. diff --git a/compiler/reporting/src/type_error.rs b/compiler/reporting/src/type_error.rs index 36db385824..f1adbc4622 100644 --- a/compiler/reporting/src/type_error.rs +++ b/compiler/reporting/src/type_error.rs @@ -29,6 +29,54 @@ pub fn type_problem<'b>( CircularType(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), + } + } } } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index bd42fea495..a9414222b9 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -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! + "# + ), + ) + } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 29321200d8..488fdaa912 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -21,6 +21,7 @@ pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), BadPattern(Region, PatternCategory, ErrorType, PExpected), CircularType(Region, Symbol, ErrorType), + BadType(roc_types::types::Problem), } pub type SubsByModule = MutMap; @@ -166,6 +167,13 @@ fn solve( problems.push(problem); + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + state } } @@ -228,6 +236,13 @@ fn solve( problems.push(problem); + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + state } } @@ -278,6 +293,13 @@ fn solve( problems.push(problem); + state + } + BadType(vars, problem) => { + introduce(subs, rank, pools, &vars); + + problems.push(TypeError::BadType(problem)); + state } } @@ -829,7 +851,7 @@ fn circular_error( loc_var: &Located, ) { 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); subs.set_content(var, Content::Error); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index c2af5b4d04..1ac2d64b4b 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -37,9 +37,10 @@ impl fmt::Debug for Mark { } #[derive(Default)] -struct NameState { +struct ErrorTypeState { taken: MutSet, normals: u32, + problems: Vec, } #[derive(Default, Clone)] @@ -363,7 +364,7 @@ impl Subs { 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) { let names = get_var_names(self, var, ImMap::default()); let mut taken = MutSet::default(); @@ -371,9 +372,13 @@ impl Subs { 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) { @@ -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); 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( subs: &mut Subs, - state: &mut NameState, + state: &mut ErrorTypeState, var: Variable, content: Content, ) -> 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::*; 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), - 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); state.normals = new_index; diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 0c6db6cc57..bb026af3c6 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -395,6 +395,7 @@ impl Type { pub fn instantiate_aliases( &mut self, + region: Region, aliases: &ImMap, var_store: &VarStore, introduced: &mut ImSet, @@ -404,34 +405,44 @@ impl Type { match self { Function(args, ret) => { 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) => { for (_, args) in tags { 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) => { 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) => { 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) => { 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 named_args = Vec::with_capacity(args.len()); @@ -447,7 +458,7 @@ impl Type { ) in alias.vars.iter().zip(args.iter()) { 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())); substitution.insert(*placeholder, filler); } @@ -463,7 +474,7 @@ impl Type { } actual.substitute(&substitution); - actual.instantiate_aliases(aliases, var_store, introduced); + actual.instantiate_aliases(region, aliases, var_store, introduced); // instantiate recursion variable! if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual { @@ -487,7 +498,7 @@ impl Type { } else { // one of the special-cased Apply types. 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)] pub enum Problem { CanonicalizationProblem, - Mismatch(Mismatch, ErrorType, ErrorType), CircularType(Symbol, ErrorType, Region), + CyclicAlias(Symbol, Region, Vec), UnrecognizedIdent(InlinableString), Shadowed(Region, Located), + BadTypeArguments { + symbol: Symbol, + region: Region, + type_got: u8, + alias_needs: u8, + }, InvalidModule, + // Mismatch(Mismatch, ErrorType, ErrorType), } #[derive(PartialEq, Eq, Debug, Clone)] diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index e0d1b25002..d7157557e9 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -73,6 +73,7 @@ struct Context { pub enum Unified { Success(Pool), Failure(Pool, ErrorType, ErrorType), + BadType(Pool, roc_types::types::Problem), } #[derive(Debug)] @@ -91,11 +92,18 @@ pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified { if mismatches.is_empty() { Unified::Success(vars) } else { - let type1 = subs.var_to_error_type(var1); - let type2 = subs.var_to_error_type(var2); + let (type1, mut problems) = subs.var_to_error_type(var1); + let (type2, problems2) = subs.var_to_error_type(var2); + + problems.extend(problems2); subs.union(var1, var2, Content::Error.into()); - Unified::Failure(vars, type1, type2) + + if !problems.is_empty() { + Unified::BadType(vars, problems.remove(0)) + } else { + Unified::Failure(vars, type1, type2) + } } }