Format has-derived clauses

This commit is contained in:
Ayaz Hafiz 2022-05-19 16:50:17 -04:00
parent fcf464e9da
commit e78b345ee0
No known key found for this signature in database
GPG Key ID: 0E2A37416A25EF58
5 changed files with 179 additions and 15 deletions

View File

@ -4,7 +4,8 @@ use crate::{
Buf, Buf,
}; };
use roc_parse::ast::{ use roc_parse::ast::{
AssignedField, Collection, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation, TypeHeader, AssignedField, Collection, Derived, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation,
TypeHeader,
}; };
use roc_parse::ident::UppercaseIdent; use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc; use roc_region::all::Loc;
@ -43,6 +44,16 @@ pub enum Newlines {
Yes, Yes,
} }
impl Newlines {
pub fn from_bool(yes: bool) -> Self {
if yes {
Self::Yes
} else {
Self::No
}
}
}
pub trait Formattable { pub trait Formattable {
fn is_multiline(&self) -> bool; fn is_multiline(&self) -> bool;
@ -546,3 +557,42 @@ impl<'a> Formattable for HasClause<'a> {
.format_with_options(buf, parens, newlines, indent); .format_with_options(buf, parens, newlines, indent);
} }
} }
impl<'a> Formattable for Derived<'a> {
fn is_multiline(&self) -> bool {
match self {
Derived::SpaceAfter(..) | Derived::SpaceBefore(..) => true,
Derived::Has(derived) => derived.is_multiline(),
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
match self {
Derived::Has(derived) => {
if newlines == Newlines::Yes {
buf.newline();
buf.indent(indent);
}
buf.push_str("has");
buf.spaces(1);
fmt_collection(buf, indent, '[', ']', *derived, newlines);
}
Derived::SpaceBefore(derived, spaces) => {
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
derived.format_with_options(buf, parens, Newlines::No, indent)
}
Derived::SpaceAfter(derived, spaces) => {
derived.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
}
}
}

View File

@ -2,7 +2,9 @@ use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern; use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, INDENT}; use crate::spaces::{fmt_spaces, INDENT};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{AbilityMember, Def, Expr, ExtractSpaces, Pattern, TypeHeader}; use roc_parse::ast::{
AbilityMember, Def, Expr, ExtractSpaces, Pattern, TypeAnnotation, TypeHeader,
};
use roc_region::all::Loc; use roc_region::all::Loc;
/// A Located formattable value is also formattable /// A Located formattable value is also formattable
@ -51,10 +53,6 @@ impl<'a> Formattable for Def<'a> {
Alias { Alias {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
ann, ann,
}
| Opaque {
header: TypeHeader { name, vars },
typ: ann,
} => { } => {
buf.indent(indent); buf.indent(indent);
buf.push_str(name.value); buf.push_str(name.value);
@ -64,15 +62,64 @@ impl<'a> Formattable for Def<'a> {
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
} }
buf.push_str(match def { buf.push_str(" :");
Alias { .. } => " :",
Opaque { .. } => " :=",
_ => unreachable!(),
});
buf.spaces(1); buf.spaces(1);
ann.format(buf, indent + INDENT) ann.format(buf, indent + INDENT)
} }
Opaque {
header: TypeHeader { name, vars },
typ: ann,
derived,
} => {
buf.indent(indent);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
}
buf.push_str(" :=");
buf.spaces(1);
let ann_is_where_clause =
matches!(ann.extract_spaces().item, TypeAnnotation::Where(..));
let ann_has_spaces_before =
matches!(&ann.value, TypeAnnotation::SpaceBefore(..));
// Always put the has-derived clause on a newline if it is itself multiline, or
// the annotation has a where-has clause.
let derived_multiline = if let Some(derived) = derived {
!derived.value.is_empty() && (derived.is_multiline() || ann_is_where_clause)
} else {
false
};
let make_multiline = ann.is_multiline() || derived_multiline;
// If the annotation has spaces before, a newline will already be printed.
if make_multiline && !ann_has_spaces_before {
buf.newline();
buf.indent(indent + INDENT);
}
ann.format(buf, indent + INDENT);
if let Some(derived) = derived {
if !make_multiline {
buf.spaces(1);
}
derived.format_with_options(
buf,
Parens::NotNeeded,
Newlines::from_bool(make_multiline),
indent + INDENT,
);
}
}
Ability { Ability {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
loc_has: _, loc_has: _,
@ -134,7 +181,6 @@ impl<'a> Formattable for Def<'a> {
body_pattern, body_pattern,
body_expr, body_expr,
} => { } => {
use roc_parse::ast::TypeAnnotation;
let is_type_multiline = ann_type.is_multiline(); let is_type_multiline = ann_type.is_multiline();
let is_type_function = matches!( let is_type_function = matches!(
ann_type.value, ann_type.value,

View File

@ -3,9 +3,9 @@ use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp}; use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{ use roc_parse::{
ast::{ ast::{
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Expr, Has, HasClause, AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, Has,
Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
ValueDef, WhenBranch, TypeHeader, ValueDef, WhenBranch,
}, },
header::{ header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
@ -438,12 +438,14 @@ impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
Opaque { Opaque {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
typ, typ,
derived,
} => Opaque { } => Opaque {
header: TypeHeader { header: TypeHeader {
name: name.remove_spaces(arena), name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena), vars: vars.remove_spaces(arena),
}, },
typ: typ.remove_spaces(arena), typ: typ.remove_spaces(arena),
derived: derived.remove_spaces(arena),
}, },
Ability { Ability {
header: TypeHeader { name, vars }, header: TypeHeader { name, vars },
@ -741,3 +743,14 @@ impl<'a> RemoveSpaces<'a> for Tag<'a> {
} }
} }
} }
impl<'a> RemoveSpaces<'a> for Derived<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Derived::Has(derived) => Derived::Has(derived.remove_spaces(arena)),
Derived::SpaceBefore(derived, _) | Derived::SpaceAfter(derived, _) => {
derived.remove_spaces(arena)
}
}
}
}

View File

@ -4658,6 +4658,46 @@ mod test_fmt {
); );
} }
#[test]
fn opaque_has_clause() {
expr_formats_same(indoc!(
r#"
A := U8 has [ Eq, Hash ]
0
"#
));
expr_formats_same(indoc!(
r#"
A :=
U8
has [ Eq, Hash ]
0
"#
));
expr_formats_to(
indoc!(
r#"
A := a | a has Hash has [ Eq, Hash ]
0
"#
),
indoc!(
r#"
A :=
a | a has Hash
has [ Eq, Hash ]
0
"#
),
);
}
#[test] #[test]
/// Test that everything under examples/ is formatted correctly /// Test that everything under examples/ is formatted correctly
/// If this test fails on your diff, it probably means you need to re-format the examples. /// If this test fails on your diff, it probably means you need to re-format the examples.

View File

@ -400,6 +400,20 @@ pub enum Derived<'a> {
SpaceAfter(&'a Derived<'a>, &'a [CommentOrNewline<'a>]), SpaceAfter(&'a Derived<'a>, &'a [CommentOrNewline<'a>]),
} }
impl Derived<'_> {
pub fn is_empty(&self) -> bool {
let mut it = self;
loop {
match it {
Self::SpaceBefore(inner, _) | Self::SpaceAfter(inner, _) => {
it = inner;
}
Self::Has(collection) => return collection.is_empty(),
}
}
}
}
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
pub enum TypeAnnotation<'a> { pub enum TypeAnnotation<'a> {
/// A function. The types of its arguments, then the type of its return value. /// A function. The types of its arguments, then the type of its return value.
@ -1005,6 +1019,7 @@ impl_extract_spaces!(Expr);
impl_extract_spaces!(Pattern); impl_extract_spaces!(Pattern);
impl_extract_spaces!(Tag); impl_extract_spaces!(Tag);
impl_extract_spaces!(AssignedField<T>); impl_extract_spaces!(AssignedField<T>);
impl_extract_spaces!(TypeAnnotation);
impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> { impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> {
type Item = T; type Item = T;