diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs index eda2d8d13e..aabec02597 100644 --- a/crates/compiler/can/src/annotation.rs +++ b/crates/compiler/can/src/annotation.rs @@ -450,7 +450,8 @@ pub fn find_type_def_symbols( stack.push(&annotation.value); for has_clause in clauses.iter() { - stack.push(&has_clause.value.ability.value); + // TODO(abilities) + stack.push(&has_clause.value.abilities[0].value); } } Inferred | Wildcard | Malformed(_) => {} @@ -920,7 +921,7 @@ fn canonicalize_has_clause( ) -> Result<(), Type> { let Loc { region, - value: roc_parse::ast::HasClause { var, ability }, + value: roc_parse::ast::HasClause { var, abilities }, } = clause; let region = *region; @@ -931,6 +932,8 @@ fn canonicalize_has_clause( ); let var_name = Lowercase::from(var_name); + // TODO(abilities) + let ability = abilities[0]; let ability = match ability.value { TypeAnnotation::Apply(module_name, ident, _type_arguments) => { let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?; diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index 3c0fb8d5b9..95e73878ea 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -546,7 +546,8 @@ impl<'a> Formattable for Tag<'a> { impl<'a> Formattable for HasClause<'a> { fn is_multiline(&self) -> bool { - self.ability.is_multiline() + // TODO(abilities) + self.abilities[0].is_multiline() } fn format_with_options<'buf>( @@ -560,8 +561,8 @@ impl<'a> Formattable for HasClause<'a> { buf.spaces(1); buf.push_str("has"); buf.spaces(1); - self.ability - .format_with_options(buf, parens, newlines, indent); + // TODO(abilities) + self.abilities[0].format_with_options(buf, parens, newlines, indent); } } diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs index 574b1aec3a..f8b3009f7f 100644 --- a/crates/compiler/fmt/src/spaces.rs +++ b/crates/compiler/fmt/src/spaces.rs @@ -795,7 +795,7 @@ impl<'a> RemoveSpaces<'a> for HasClause<'a> { fn remove_spaces(&self, arena: &'a Bump) -> Self { HasClause { var: self.var.remove_spaces(arena), - ability: self.ability.remove_spaces(arena), + abilities: self.abilities.remove_spaces(arena), } } } diff --git a/crates/compiler/load_internal/src/docs.rs b/crates/compiler/load_internal/src/docs.rs index a702747b7f..41815f0594 100644 --- a/crates/compiler/load_internal/src/docs.rs +++ b/crates/compiler/load_internal/src/docs.rs @@ -394,10 +394,11 @@ fn ability_member_type_to_docs( let has_clauses = has_clauses .iter() .map(|hc| { - let ast::HasClause { var, ability } = hc.value; + let ast::HasClause { var, abilities } = hc.value; ( var.value.extract_spaces().item.to_string(), - type_to_docs(false, ability.value), + // TODO(abilities) + type_to_docs(false, abilities[0].value), ) }) .collect(); diff --git a/crates/compiler/parse/src/ast.rs b/crates/compiler/parse/src/ast.rs index 46fe42222d..0c085a8d70 100644 --- a/crates/compiler/parse/src/ast.rs +++ b/crates/compiler/parse/src/ast.rs @@ -439,7 +439,7 @@ pub type AbilityName<'a> = Loc>; #[derive(Debug, Copy, Clone, PartialEq)] pub struct HasClause<'a> { pub var: Loc>, - pub ability: AbilityName<'a>, + pub abilities: &'a [AbilityName<'a>], } #[derive(Debug, Copy, Clone, PartialEq)] diff --git a/crates/compiler/parse/src/type_annotation.rs b/crates/compiler/parse/src/type_annotation.rs index f88d0e2d90..51dfdbd0db 100644 --- a/crates/compiler/parse/src/type_annotation.rs +++ b/crates/compiler/parse/src/type_annotation.rs @@ -2,7 +2,9 @@ use crate::ast::{ AssignedField, CommentOrNewline, HasAbilities, HasAbility, HasClause, HasImpls, Pattern, Spaced, Tag, TypeAnnotation, TypeHeader, }; -use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; +use crate::blankspace::{ + space0_around_ee, space0_before_e, space0_before_optional_after, space0_e, +}; use crate::expr::record_value_field; use crate::ident::lowercase_ident; use crate::keyword; @@ -410,6 +412,37 @@ fn loc_applied_args_e<'a>( zero_or_more!(loc_applied_arg(min_indent, stop_at_surface_has)) } +// Hash & Eq & ... +fn ability_chain<'a>( + min_indent: u32, +) -> impl Parser<'a, Vec<'a, Loc>>, EType<'a>> { + map!( + and!( + space0_before_optional_after( + specialize(EType::TApply, loc!(parse_concrete_type)), + min_indent, + EType::TIndentStart, + EType::TIndentEnd, + ), + zero_or_more!(skip_first!( + word1(b'&', EType::THasClause), + space0_before_e( + specialize(EType::TApply, loc!(parse_concrete_type)), + min_indent, + EType::TIndentStart, + ) + )) + ), + |(first_ability, mut other_abilities): ( + Loc>, + Vec<'a, Loc>> + )| { + other_abilities.push(first_ability); + other_abilities + } + ) +} + fn has_clause<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { map!( // Suppose we are trying to parse "a has Hash" @@ -427,20 +460,22 @@ fn has_clause<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType< then( // Parse "has"; we don't care about this keyword word3(b'h', b'a', b's', EType::THasClause), - // Parse "Hash"; this may be qualified from another module like "Hash.Hash" + // Parse "Hash & ..."; this may be qualified from another module like "Hash.Hash" |arena, state, _progress, _output| { - space0_before_e( - specialize(EType::TApply, loc!(parse_concrete_type)), - state.column() + 1, - EType::TIndentStart, - ) - .parse(arena, state) + ability_chain(state.column() + 1).parse(arena, state) } ) ), - |(var, ability): (Loc>, Loc>)| { - let region = Region::span_across(&var.region, &ability.region); - let has_clause = HasClause { var, ability }; + |(var, abilities): (Loc>, Vec<'a, Loc>>)| { + let abilities_region = Region::span_across( + &abilities.first().unwrap().region, + &abilities.last().unwrap().region, + ); + let region = Region::span_across(&var.region, &abilities_region); + let has_clause = HasClause { + var, + abilities: abilities.into_bump_slice(), + }; Loc::at(region, has_clause) } ) diff --git a/crates/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast index 78bc7bb229..e450865a13 100644 --- a/crates/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/ability_single_line.expr.result-ast @@ -39,11 +39,13 @@ Defs( [ @27-37 HasClause { var: @27-28 "a", - ability: @33-37 Apply( - "", - "Hash", - [], - ), + abilities: [ + @33-37 Apply( + "", + "Hash", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast index c169f89997..cce2f989e2 100644 --- a/crates/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast @@ -45,11 +45,13 @@ Defs( [ @24-33 HasClause { var: @24-25 "a", - ability: @30-33 Apply( - "", - "Ab1", - [], - ), + abilities: [ + @30-33 Apply( + "", + "Ab1", + [], + ), + ], }, ], ), @@ -80,11 +82,13 @@ Defs( [ @59-68 HasClause { var: @59-60 "a", - ability: @65-68 Apply( - "", - "Ab2", - [], - ), + abilities: [ + @65-68 Apply( + "", + "Ab2", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast index 1741e99fb7..b9a9bf4520 100644 --- a/crates/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast @@ -114,11 +114,13 @@ Defs( [ @33-44 HasClause { var: @33-34 "a", - ability: @39-44 Apply( - "", - "Other", - [], - ), + abilities: [ + @39-44 Apply( + "", + "Other", + [], + ), + ], }, ], ), @@ -157,11 +159,13 @@ Defs( [ @70-81 HasClause { var: @70-71 "a", - ability: @76-81 Apply( - "", - "Other", - [], - ), + abilities: [ + @76-81 Apply( + "", + "Other", + [], + ), + ], }, ], ), @@ -394,11 +398,13 @@ Defs( [ @260-271 HasClause { var: @260-261 "a", - ability: @266-271 Apply( - "", - "Other", - [], - ), + abilities: [ + @266-271 Apply( + "", + "Other", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast index 05be1444fc..f06dab0871 100644 --- a/crates/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/where_clause_function.expr.result-ast @@ -40,11 +40,13 @@ Defs( [ @20-27 HasClause { var: @20-21 "a", - ability: @26-27 Apply( - "", - "A", - [], - ), + abilities: [ + @26-27 Apply( + "", + "A", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast index c2db4018eb..05f6b2b66d 100644 --- a/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has.expr.result-ast @@ -40,27 +40,33 @@ Defs( [ @20-27 HasClause { var: @20-21 "a", - ability: @26-27 Apply( - "", - "A", - [], - ), + abilities: [ + @26-27 Apply( + "", + "A", + [], + ), + ], }, @29-37 HasClause { var: @29-30 "b", - ability: @35-37 Apply( - "", - "Eq", - [], - ), + abilities: [ + @35-37 Apply( + "", + "Eq", + [], + ), + ], }, @39-48 HasClause { var: @39-40 "c", - ability: @45-48 Apply( - "", - "Ord", - [], - ), + abilities: [ + @45-48 Apply( + "", + "Ord", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast index 727ca1021c..c12a8c5738 100644 --- a/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/where_clause_multiple_has_across_newlines.expr.result-ast @@ -45,11 +45,13 @@ Defs( [ @24-34 HasClause { var: @24-25 "a", - ability: @30-34 Apply( - "", - "Hash", - [], - ), + abilities: [ + @30-34 Apply( + "", + "Hash", + [], + ), + ], }, @42-50 HasClause { var: @42-43 SpaceBefore( @@ -58,11 +60,13 @@ Defs( Newline, ], ), - ability: @48-50 Apply( - "", - "Eq", - [], - ), + abilities: [ + @48-50 Apply( + "", + "Eq", + [], + ), + ], }, @58-67 HasClause { var: @58-59 SpaceBefore( @@ -71,11 +75,13 @@ Defs( Newline, ], ), - ability: @64-67 Apply( - "", - "Ord", - [], - ), + abilities: [ + @64-67 Apply( + "", + "Ord", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast index 64d9e8cadc..862b258bf8 100644 --- a/crates/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/where_clause_non_function.expr.result-ast @@ -26,11 +26,13 @@ Defs( [ @8-15 HasClause { var: @8-9 "a", - ability: @14-15 Apply( - "", - "A", - [], - ), + abilities: [ + @14-15 Apply( + "", + "A", + [], + ), + ], }, ], ), diff --git a/crates/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast b/crates/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast index c216d31d1b..b0f7fb62ab 100644 --- a/crates/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast +++ b/crates/compiler/parse/tests/snapshots/pass/where_clause_on_newline.expr.result-ast @@ -40,11 +40,13 @@ Defs( [ @19-29 HasClause { var: @19-20 "a", - ability: @25-29 Apply( - "", - "Hash", - [], - ), + abilities: [ + @25-29 Apply( + "", + "Hash", + [], + ), + ], }, ], ),