diff --git a/crates/compiler/types/src/subs.rs b/crates/compiler/types/src/subs.rs index f13b4f0db4..77c45aad3b 100644 --- a/crates/compiler/types/src/subs.rs +++ b/crates/compiler/types/src/subs.rs @@ -3738,14 +3738,14 @@ fn content_to_err_type( } }; - // TODO(multi-abilities) - ErrorType::FlexAbleVar(name, subs.get_subs_slice(abilities)[0]) + let ability_set = AbilitySet::from_iter(subs.get_subs_slice(abilities).iter().copied()); + ErrorType::FlexAbleVar(name, ability_set) } RigidAbleVar(name_index, abilities) => { let name = subs.field_names[name_index.index as usize].clone(); - // TODO(multi-abilities) - ErrorType::RigidAbleVar(name, subs.get_subs_slice(abilities)[0]) + let ability_set = AbilitySet::from_iter(subs.get_subs_slice(abilities).iter().copied()); + ErrorType::RigidAbleVar(name, ability_set) } RecursionVar { diff --git a/crates/compiler/types/src/types.rs b/crates/compiler/types/src/types.rs index 14faf82cdb..e1c71eb8e0 100644 --- a/crates/compiler/types/src/types.rs +++ b/crates/compiler/types/src/types.rs @@ -256,7 +256,7 @@ pub struct AliasCommon { /// /// In the future we might want to do some small-vec optimizations, though that may be trivialized /// away with a SoA representation of canonicalized types. -#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord)] +#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash)] pub struct AbilitySet(Vec); impl AbilitySet { @@ -282,11 +282,11 @@ impl AbilitySet { self.0.contains(ability) } - pub fn sorted_iter(&self) -> impl Iterator { + pub fn sorted_iter(&self) -> impl ExactSizeIterator { self.0.iter() } - pub fn into_sorted_iter(self) -> impl Iterator { + pub fn into_sorted_iter(self) -> impl ExactSizeIterator { self.0.into_iter() } } @@ -2266,8 +2266,8 @@ pub enum ErrorType { Type(Symbol, Vec), FlexVar(Lowercase), RigidVar(Lowercase), - FlexAbleVar(Lowercase, Symbol), - RigidAbleVar(Lowercase, Symbol), + FlexAbleVar(Lowercase, AbilitySet), + RigidAbleVar(Lowercase, AbilitySet), Record(SendMap>, TypeExt), TagUnion(SendMap>, TypeExt), RecursiveTagUnion(Box, SendMap>, TypeExt), diff --git a/crates/reporting/src/error/type.rs b/crates/reporting/src/error/type.rs index 7cb820c4b8..43774ada56 100644 --- a/crates/reporting/src/error/type.rs +++ b/crates/reporting/src/error/type.rs @@ -15,7 +15,7 @@ use roc_solve_problem::{ use roc_std::RocDec; use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{ - AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt, + AbilitySet, AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt, }; use std::path::PathBuf; use ven_pretty::DocAllocator; @@ -2050,7 +2050,7 @@ pub enum Problem { FieldsMissing(Vec), TagTypo(TagName, Vec), TagsMissing(Vec), - BadRigidVar(Lowercase, ErrorType, Option), + BadRigidVar(Lowercase, ErrorType, Option), OptionalRequiredMismatch(Lowercase), OpaqueComparedToNonOpaque, BoolVsBoolTag(TagName), @@ -2208,7 +2208,7 @@ fn ext_to_doc<'b>(alloc: &'b RocDocAllocator<'b>, ext: TypeExt) -> Option; +type AbleVariables = Vec<(Lowercase, AbilitySet)>; #[derive(Default)] struct Context { @@ -2249,7 +2249,6 @@ fn to_doc_help<'b>( FlexVar(lowercase) | RigidVar(lowercase) => alloc.type_variable(lowercase), FlexAbleVar(lowercase, ability) | RigidAbleVar(lowercase, ability) => { - // TODO we should be putting able variables on the toplevel of the type, not here ctx.able_variables.push((lowercase.clone(), ability)); alloc.type_variable(lowercase) } @@ -2424,13 +2423,20 @@ fn type_with_able_vars<'b>( let mut doc = Vec::with_capacity(1 + 6 * able.len()); doc.push(typ); - for (i, (var, ability)) in able.into_iter().enumerate() { + for (i, (var, abilities)) in able.into_iter().enumerate() { doc.push(alloc.string(if i == 0 { " | " } else { ", " }.to_string())); doc.push(alloc.type_variable(var)); doc.push(alloc.space()); doc.push(alloc.keyword("has")); - doc.push(alloc.space()); - doc.push(alloc.symbol_foreign_qualified(ability)); + + for (i, ability) in abilities.into_sorted_iter().enumerate() { + if i > 0 { + doc.push(alloc.space()); + doc.push(alloc.text("&")); + } + doc.push(alloc.space()); + doc.push(alloc.symbol_foreign_qualified(ability)); + } } alloc.concat(doc) @@ -2501,14 +2507,14 @@ fn to_diff<'b>( } } - (RigidAbleVar(x, ab), other) | (other, RigidAbleVar(x, ab)) => { + (RigidAbleVar(x, abs), other) | (other, RigidAbleVar(x, abs)) => { let (left, left_able) = to_doc(alloc, Parens::InFn, type1); let (right, right_able) = to_doc(alloc, Parens::InFn, type2); Diff { left, right, - status: Status::Different(vec![Problem::BadRigidVar(x, other, Some(ab))]), + status: Status::Different(vec![Problem::BadRigidVar(x, other, Some(abs))]), left_able, right_able, } @@ -3554,11 +3560,25 @@ fn type_problem_to_pretty<'b>( let bad_rigid_var = |name: Lowercase, a_thing| { let kind_of_value = match opt_ability { - Some(ability) => alloc.concat([ - alloc.reflow("any value implementing the "), - alloc.symbol_unqualified(ability), - alloc.reflow(" ability"), - ]), + Some(abilities) => { + let mut abilities = abilities.into_sorted_iter(); + if abilities.len() == 1 { + alloc.concat([ + alloc.reflow("any value implementing the "), + alloc.symbol_unqualified(abilities.next().unwrap()), + alloc.reflow(" ability"), + ]) + } else { + alloc.concat([ + alloc.reflow("any value implementing the "), + alloc.intersperse( + abilities.map(|ab| alloc.symbol_unqualified(ab)), + alloc.reflow(", "), + ), + alloc.reflow(" abilities"), + ]) + } + } None => alloc.reflow("any type of value"), }; alloc @@ -3609,13 +3629,25 @@ fn type_problem_to_pretty<'b>( match tipe { Infinite | Error | FlexVar(_) => alloc.nil(), - FlexAbleVar(_, ability) => bad_rigid_var( - x, - alloc.concat([ - alloc.reflow("an instance of the ability "), - alloc.symbol_unqualified(ability), - ]), - ), + FlexAbleVar(_, abilities) => { + let mut abilities = abilities.into_sorted_iter(); + let msg = if abilities.len() == 1 { + alloc.concat([ + alloc.reflow("an instance of the ability "), + alloc.symbol_unqualified(abilities.next().unwrap()), + ]) + } else { + alloc.concat([ + alloc.reflow("an instance of the "), + alloc.intersperse( + abilities.map(|ab| alloc.symbol_unqualified(ab)), + alloc.reflow(", "), + ), + alloc.reflow(" abilities"), + ]) + }; + bad_rigid_var(x, msg) + } RigidVar(y) | RigidAbleVar(y, _) => bad_double_rigid(x, y), Function(_, _, _) => bad_rigid_var(x, alloc.reflow("a function value")), Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")),