moved all crates into seperate folder + related path fixes

This commit is contained in:
Anton-4 2022-07-01 17:37:43 +02:00
parent 12ef03bb86
commit eee85fa45d
No known key found for this signature in database
GPG key ID: C954D6E0F9C0ABFD
1063 changed files with 92 additions and 93 deletions

View file

@ -0,0 +1,601 @@
use crate::{
collection::{fmt_collection, Braces},
spaces::{fmt_comments_only, fmt_spaces, NewlineAt, INDENT},
Buf,
};
use roc_parse::ast::{
AssignedField, Collection, Derived, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation,
TypeHeader,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
/// Does an AST node need parens around it?
///
/// Usually not, but there are two cases where it may be required
///
/// 1. In a function type, function types are in parens
///
/// a -> b, c -> d
/// (a -> b), c -> d
///
/// 2. In applications, applications are in brackets
/// This is true in patterns, type annotations and expressions
///
/// Just (Just a)
/// List (List a)
/// reverse (reverse l)
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Parens {
NotNeeded,
InFunctionType,
InApply,
}
/// In an AST node, do we show newlines around it
///
/// Sometimes, we only want to show comments, at other times
/// we also want to show newlines. By default the formatter
/// takes care of inserting newlines, but sometimes the user's
/// newlines are taken into account.
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Newlines {
No,
Yes,
}
impl Newlines {
pub fn from_bool(yes: bool) -> Self {
if yes {
Self::Yes
} else {
Self::No
}
}
}
pub trait Formattable {
fn is_multiline(&self) -> bool;
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
self.format(buf, indent);
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
/// A reference to a formattable value is also formattable
impl<'a, T> Formattable for &'a T
where
T: Formattable,
{
fn is_multiline(&self) -> bool {
(*self).is_multiline()
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
(*self).format_with_options(buf, parens, newlines, indent)
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
(*self).format(buf, indent)
}
}
impl<'a, T> Formattable for Collection<'a, T>
where
T: Formattable,
{
fn is_multiline(&self) -> bool {
// if there are any comments, they must go on their own line
// because otherwise they'd comment out the closing delimiter
!self.final_comments().is_empty() ||
// if any of the items in the collection are multiline,
// then the whole collection must be multiline
self.items.iter().any(Formattable::is_multiline)
}
}
/// A Located formattable value is also formattable
impl<T> Formattable for Loc<T>
where
T: Formattable,
{
fn is_multiline(&self) -> bool {
self.value.is_multiline()
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
self.value
.format_with_options(buf, parens, newlines, indent)
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
self.value.format(buf, indent)
}
}
impl<'a> Formattable for UppercaseIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
_indent: u16,
) {
buf.push_str((*self).into())
}
}
impl<'a> Formattable for TypeAnnotation<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeAnnotation::*;
match self {
// Return whether these spaces contain any Newlines
SpaceBefore(_, spaces) | SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
// "spaces" always contain either a newline or comment, and comments have newlines
true
}
Wildcard | Inferred | BoundVariable(_) | Malformed(_) => false,
Function(args, result) => {
(&result.value).is_multiline()
|| args.iter().any(|loc_arg| (&loc_arg.value).is_multiline())
}
Apply(_, _, args) => args.iter().any(|loc_arg| loc_arg.value.is_multiline()),
As(lhs, _, _) => lhs.value.is_multiline(),
Where(annot, has_clauses) => {
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
}
Record { fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
fields.items.iter().any(|field| field.value.is_multiline())
}
TagUnion { tags, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
}
tags.iter().any(|tag| tag.value.is_multiline())
}
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::TypeAnnotation::*;
match self {
Function(arguments, result) => {
let write_parens = parens != Parens::NotNeeded;
if write_parens {
buf.push('(')
}
let mut it = arguments.iter().enumerate().peekable();
let should_add_newlines = newlines == Newlines::Yes;
while let Some((index, argument)) = it.next() {
let is_first = index == 0;
let is_multiline = &argument.value.is_multiline();
if !is_first && !is_multiline && should_add_newlines {
buf.newline();
}
(&argument.value).format_with_options(
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if it.peek().is_some() {
buf.push_str(",");
if !should_add_newlines {
buf.spaces(1);
}
}
}
if should_add_newlines {
buf.newline();
buf.indent(indent);
} else {
buf.spaces(1);
}
buf.push_str("->");
buf.spaces(1);
(&result.value).format_with_options(
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if write_parens {
buf.push(')')
}
}
Apply(pkg, name, arguments) => {
buf.indent(indent);
// NOTE apply is never multiline
let write_parens = parens == Parens::InApply && !arguments.is_empty();
if write_parens {
buf.push('(')
}
if !pkg.is_empty() {
buf.push_str(pkg);
buf.push('.');
}
buf.push_str(name);
for argument in *arguments {
buf.spaces(1);
(&argument.value).format_with_options(
buf,
Parens::InApply,
Newlines::No,
indent,
);
}
if write_parens {
buf.push(')')
}
}
BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'),
Inferred => buf.push('_'),
TagUnion { tags, ext } => {
fmt_collection(buf, indent, Braces::Square, *tags, newlines);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record { fields, ext } => {
fmt_collection(buf, indent, Braces::Curly, *fields, newlines);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
As(lhs, _spaces, TypeHeader { name, vars }) => {
// TODO use _spaces?
lhs.value
.format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
buf.spaces(1);
buf.push_str("as");
buf.spaces(1);
buf.push_str(name.value);
for var in *vars {
buf.spaces(1);
var.value
.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
}
}
Where(annot, has_clauses) => {
annot.format_with_options(buf, parens, newlines, indent);
buf.spaces(1);
for (i, has) in has_clauses.iter().enumerate() {
buf.push(if i == 0 { '|' } else { ',' });
buf.spaces(1);
has.format_with_options(buf, parens, newlines, indent);
}
}
SpaceBefore(ann, spaces) => {
let is_function = matches!(ann, TypeAnnotation::Function(..));
let next_newlines = if is_function && newlines == Newlines::Yes {
Newlines::Yes
} else {
Newlines::No
};
buf.newline();
buf.indent(indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
ann.format_with_options(buf, parens, next_newlines, indent)
}
SpaceAfter(ann, spaces) => {
ann.format_with_options(buf, parens, newlines, indent);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
Malformed(raw) => buf.push_str(raw),
}
}
}
/// Fields are subtly different on the type and term level:
///
/// > type: { x : Int, y : Bool }
/// > term: { x: 100, y: True }
///
/// So we need two instances, each having the specific separator
impl<'a> Formattable for AssignedField<'a, TypeAnnotation<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, 1, newlines == Newlines::Yes);
}
}
impl<'a> Formattable for AssignedField<'a, Expr<'a>> {
fn is_multiline(&self) -> bool {
is_multiline_assigned_field_help(self)
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
// we abuse the `Newlines` type to decide between multiline or single-line layout
format_assigned_field_help(self, buf, parens, indent, 0, newlines == Newlines::Yes);
}
}
fn is_multiline_assigned_field_help<T: Formattable>(afield: &AssignedField<'_, T>) -> bool {
use self::AssignedField::*;
match afield {
RequiredValue(_, spaces, ann) | OptionalValue(_, spaces, ann) => {
!spaces.is_empty() || ann.value.is_multiline()
}
LabelOnly(_) => false,
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_assigned_field_help<'a, 'buf, T>(
zelf: &AssignedField<'a, T>,
buf: &mut Buf<'buf>,
parens: Parens,
indent: u16,
separator_spaces: usize,
is_multiline: bool,
) where
T: Formattable,
{
use self::AssignedField::*;
match zelf {
RequiredValue(name, spaces, ann) => {
if is_multiline {
buf.newline();
}
buf.indent(indent);
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.spaces(separator_spaces);
buf.push_str(":");
buf.spaces(1);
ann.value.format(buf, indent);
}
OptionalValue(name, spaces, ann) => {
if is_multiline {
buf.newline();
buf.indent(indent);
}
buf.push_str(name.value);
if !spaces.is_empty() {
fmt_spaces(buf, spaces.iter(), indent);
}
buf.spaces(separator_spaces);
buf.push('?');
ann.value.format(buf, indent);
}
LabelOnly(name) => {
if is_multiline {
buf.newline();
buf.indent(indent);
}
buf.push_str(name.value);
}
AssignedField::SpaceBefore(sub_field, spaces) => {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
format_assigned_field_help(
sub_field,
buf,
parens,
indent,
separator_spaces,
is_multiline,
);
}
AssignedField::SpaceAfter(sub_field, spaces) => {
format_assigned_field_help(
sub_field,
buf,
parens,
indent,
separator_spaces,
is_multiline,
);
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
}
Malformed(raw) => {
buf.push_str(raw);
}
}
}
impl<'a> Formattable for Tag<'a> {
fn is_multiline(&self) -> bool {
use self::Tag::*;
match self {
Apply { args, .. } => args.iter().any(|arg| (&arg.value).is_multiline()),
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => true,
Malformed(text) => text.chars().any(|c| c == '\n'),
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
let is_multiline = self.is_multiline();
match self {
Tag::Apply { name, args } => {
buf.indent(indent);
buf.push_str(name.value);
if is_multiline {
let arg_indent = indent + INDENT;
for arg in *args {
buf.newline();
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
}
} else {
for arg in *args {
buf.spaces(1);
arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
}
}
Tag::SpaceBefore(_, _) | Tag::SpaceAfter(_, _) => unreachable!(),
Tag::Malformed(raw) => {
buf.indent(indent);
buf.push_str(raw);
}
}
}
}
impl<'a> Formattable for HasClause<'a> {
fn is_multiline(&self) -> bool {
self.var.value.is_multiline() || self.ability.is_multiline()
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
buf.push_str(self.var.value.extract_spaces().item);
buf.spaces(1);
buf.push_str("has");
buf.spaces(1);
self.ability
.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, Braces::Square, *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

@ -0,0 +1,110 @@
use roc_parse::ast::{Collection, ExtractSpaces};
use crate::{
annotation::{Formattable, Newlines},
spaces::{count_leading_newlines, fmt_comments_only, NewlineAt, INDENT},
Buf,
};
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Braces {
Square,
Curly,
}
pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
buf: &mut Buf<'buf>,
indent: u16,
braces: Braces,
items: Collection<'a, T>,
newline: Newlines,
) where
<T as ExtractSpaces<'a>>::Item: Formattable,
{
let start = match braces {
Braces::Curly => '{',
Braces::Square => '[',
};
let end = match braces {
Braces::Curly => '}',
Braces::Square => ']',
};
if items.is_multiline() {
let braces_indent = indent;
let item_indent = braces_indent + INDENT;
if newline == Newlines::Yes {
buf.newline();
}
buf.indent(braces_indent);
buf.push(start);
for (index, item) in items.iter().enumerate() {
let item = item.extract_spaces();
let is_first_item = index == 0;
buf.newline();
if !item.before.is_empty() {
let is_only_newlines = item.before.iter().all(|s| s.is_newline());
if !is_first_item
&& !is_only_newlines
&& count_leading_newlines(item.before.iter()) > 1
{
buf.newline();
}
fmt_comments_only(buf, item.before.iter(), NewlineAt::Bottom, item_indent);
if !is_only_newlines && count_leading_newlines(item.before.iter().rev()) > 0 {
buf.newline();
}
}
item.item.format(buf, item_indent);
buf.push(',');
if !item.after.is_empty() {
fmt_comments_only(buf, item.after.iter(), NewlineAt::Top, item_indent);
}
}
if count_leading_newlines(items.final_comments().iter()) > 1 {
buf.newline();
}
fmt_comments_only(
buf,
items.final_comments().iter(),
NewlineAt::Top,
item_indent,
);
buf.newline();
buf.indent(braces_indent);
} else {
// is_multiline == false
// there is no comment to add
buf.indent(indent);
buf.push(start);
let mut iter = items.iter().enumerate().peekable();
while let Some((index, item)) = iter.next() {
if braces == Braces::Curly || index != 0 {
buf.spaces(1);
}
item.format(buf, indent);
if iter.peek().is_some() {
buf.push(',');
}
}
if !items.is_empty() && braces == Braces::Curly {
buf.spaces(1);
}
}
buf.push(end);
}

View file

@ -0,0 +1,421 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::pattern::fmt_pattern;
use crate::spaces::{fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{
AbilityMember, Def, Defs, Expr, ExtractSpaces, Pattern, TypeAnnotation, TypeDef, TypeHeader,
ValueDef,
};
use roc_region::all::Loc;
/// A Located formattable value is also formattable
impl<'a> Formattable for Defs<'a> {
fn is_multiline(&self) -> bool {
!self.tags.is_empty()
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
for (index, def) in self.defs().enumerate() {
let spaces_before = &self.spaces[self.space_before[index].indices()];
let spaces_after = &self.spaces[self.space_after[index].indices()];
fmt_spaces(buf, spaces_before.iter(), indent);
match def {
Ok(type_def) => type_def.format(buf, indent),
Err(value_def) => value_def.format(buf, indent),
}
fmt_spaces(buf, spaces_after.iter(), indent);
}
}
}
impl<'a> Formattable for TypeDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::TypeDef::*;
match self {
Alias { ann, .. } => ann.is_multiline(),
Opaque { typ, .. } => typ.is_multiline(),
Ability { members, .. } => members.iter().any(|d| d.is_multiline()),
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::TypeDef::*;
match self {
Alias {
header: TypeHeader { name, vars },
ann,
} => {
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);
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 {
header: TypeHeader { name, vars },
loc_has: _,
members,
} => {
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(" has");
if !self.is_multiline() {
debug_assert_eq!(members.len(), 1);
buf.push_str(" ");
members[0].format(buf, indent + INDENT);
} else {
for demand in members.iter() {
buf.newline();
buf.indent(indent + INDENT);
demand.format(buf, indent + INDENT);
}
}
}
}
}
}
impl<'a> Formattable for ValueDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::ValueDef::*;
match self {
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.is_multiline() || loc_annotation.is_multiline()
}
Body(loc_pattern, loc_expr) => loc_pattern.is_multiline() || loc_expr.is_multiline(),
AnnotatedBody { .. } => true,
Expect(loc_expr) => loc_expr.is_multiline(),
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::ValueDef::*;
match self {
Annotation(loc_pattern, loc_annotation) => {
loc_pattern.format(buf, indent);
if loc_annotation.is_multiline() {
buf.push_str(" :");
let should_outdent = match loc_annotation.value {
TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def {
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines && sub_def.is_multiline()
}
_ => false,
},
TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true,
_ => false,
};
if should_outdent {
buf.spaces(1);
match loc_annotation.value {
TypeAnnotation::SpaceBefore(sub_def, _) => {
sub_def.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
_ => {
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
}
} else {
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + INDENT,
);
}
} else {
buf.spaces(1);
buf.push_str(":");
buf.spaces(1);
loc_annotation.format_with_options(
buf,
Parens::NotNeeded,
Newlines::No,
indent,
);
}
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
}
Expect(condition) => fmt_expect(buf, condition, self.is_multiline(), indent),
AnnotatedBody {
ann_pattern,
ann_type,
comment,
body_pattern,
body_expr,
} => {
let is_type_multiline = ann_type.is_multiline();
let is_type_function = matches!(
ann_type.value,
TypeAnnotation::Function(..)
| TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..)
| TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..)
);
let next_indent = if is_type_multiline {
indent + INDENT
} else {
indent
};
ann_pattern.format(buf, indent);
buf.push_str(" :");
if is_type_multiline && is_type_function {
ann_type.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
next_indent,
);
} else {
buf.spaces(1);
ann_type.format(buf, indent);
}
if let Some(comment_str) = comment {
buf.push_str(" #");
buf.spaces(1);
buf.push_str(comment_str.trim());
}
buf.newline();
fmt_body(buf, &body_pattern.value, &body_expr.value, indent);
}
}
}
}
impl<'a> Formattable for Def<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::Def::*;
match self {
Type(def) => def.is_multiline(),
Value(def) => def.is_multiline(),
SpaceBefore(sub_def, spaces) | SpaceAfter(sub_def, spaces) => {
spaces.iter().any(|s| s.is_comment()) || sub_def.is_multiline()
}
NotYetImplemented(s) => todo!("{}", s),
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::Def::*;
match self {
Type(def) => def.format_with_options(buf, parens, newlines, indent),
Value(def) => def.format_with_options(buf, parens, newlines, indent),
SpaceBefore(sub_def, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
sub_def.format(buf, indent);
}
SpaceAfter(sub_def, spaces) => {
sub_def.format(buf, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
NotYetImplemented(s) => todo!("{}", s),
}
}
}
fn fmt_expect<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc<Expr<'a>>,
is_multiline: bool,
indent: u16,
) {
let return_indent = if is_multiline {
indent + INDENT
} else {
indent
};
buf.push_str("expect");
condition.format(buf, return_indent);
}
pub fn fmt_value_def<'a, 'buf>(
buf: &mut Buf<'buf>,
def: &roc_parse::ast::ValueDef<'a>,
indent: u16,
) {
def.format(buf, indent);
}
pub fn fmt_type_def<'a, 'buf>(buf: &mut Buf<'buf>, def: &roc_parse::ast::TypeDef<'a>, indent: u16) {
def.format(buf, indent);
}
pub fn fmt_defs<'a, 'buf>(buf: &mut Buf<'buf>, defs: &Defs<'a>, indent: u16) {
defs.format(buf, indent);
}
pub fn fmt_body<'a, 'buf>(
buf: &mut Buf<'buf>,
pattern: &'a Pattern<'a>,
body: &'a Expr<'a>,
indent: u16,
) {
pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
buf.push_str(" =");
if body.is_multiline() {
match body {
Expr::SpaceBefore(sub_def, spaces) => {
let should_outdent = match sub_def {
Expr::Record { .. } | Expr::List { .. } => {
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
is_only_newlines && sub_def.is_multiline()
}
_ => false,
};
if should_outdent {
buf.spaces(1);
sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
} else {
body.format_with_options(
buf,
Parens::NotNeeded,
Newlines::Yes,
indent + INDENT,
);
}
}
_ => {
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
} else {
buf.spaces(1);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
}
}
impl<'a> Formattable for AbilityMember<'a> {
fn is_multiline(&self) -> bool {
self.name.value.is_multiline() || self.typ.is_multiline()
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
buf.push_str(self.name.value.extract_spaces().item);
buf.spaces(1);
buf.push(':');
buf.spaces(1);
self.typ.value.format(buf, indent + INDENT);
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,212 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod annotation;
pub mod collection;
pub mod def;
pub mod expr;
pub mod module;
pub mod pattern;
pub mod spaces;
use bumpalo::{collections::String, Bump};
use roc_parse::ast::Module;
#[derive(Debug)]
pub struct Ast<'a> {
pub module: Module<'a>,
pub defs: roc_parse::ast::Defs<'a>,
}
#[derive(Debug)]
pub struct Buf<'a> {
text: String<'a>,
spaces_to_flush: usize,
beginning_of_line: bool,
}
impl<'a> Buf<'a> {
pub fn new_in(arena: &'a Bump) -> Buf<'a> {
Buf {
text: String::new_in(arena),
spaces_to_flush: 0,
beginning_of_line: true,
}
}
pub fn as_str(&'a self) -> &'a str {
self.text.as_str()
}
pub fn into_bump_str(self) -> &'a str {
self.text.into_bump_str()
}
pub fn indent(&mut self, indent: u16) {
if self.beginning_of_line {
for _ in 0..indent {
self.text.push(' ');
}
}
self.beginning_of_line = false;
}
pub fn push(&mut self, ch: char) {
debug_assert!(!self.beginning_of_line);
debug_assert!(ch != '\n' && ch != ' ');
self.flush_spaces();
self.text.push(ch);
}
pub fn push_str_allow_spaces(&mut self, s: &str) {
debug_assert!(!self.beginning_of_line);
self.flush_spaces();
self.text.push_str(s);
}
pub fn push_str(&mut self, s: &str) {
debug_assert!(!self.beginning_of_line);
debug_assert!(!s.contains('\n') && !s.ends_with(' '));
if !s.is_empty() {
self.flush_spaces();
}
self.text.push_str(s);
}
pub fn spaces(&mut self, count: usize) {
self.spaces_to_flush += count;
}
pub fn newline(&mut self) {
self.spaces_to_flush = 0;
self.text.push('\n');
self.beginning_of_line = true;
}
fn flush_spaces(&mut self) {
if self.spaces_to_flush > 0 {
for _ in 0..self.spaces_to_flush {
self.text.push(' ');
}
self.spaces_to_flush = 0;
}
}
/// Ensures the text ends in a newline with no whitespace preceding it.
pub fn fmt_end_of_file(&mut self) {
fmt_text_eof(&mut self.text)
}
}
/// Ensures the text ends in a newline with no whitespace preceding it.
fn fmt_text_eof(text: &mut bumpalo::collections::String<'_>) {
let mut chars_rev = text.chars().rev();
let mut last_whitespace = None;
let mut last_whitespace_index = text.len();
// Keep going until we either run out of characters or encounter one
// that isn't whitespace.
loop {
match chars_rev.next() {
Some(ch) if ch.is_whitespace() => {
last_whitespace = Some(ch);
last_whitespace_index -= 1;
}
_ => {
break;
}
}
}
match last_whitespace {
Some('\n') => {
// There may have been more whitespace after this newline; remove it!
text.truncate(last_whitespace_index + '\n'.len_utf8());
}
Some(_) => {
// There's some whitespace at the end of this file, but the first
// whitespace char after the last non-whitespace char isn't a newline.
// So replace that whitespace char (and everything after it) with a newline.
text.replace_range(last_whitespace_index.., "\n");
}
None => {
debug_assert!(last_whitespace_index == text.len());
debug_assert!(!text.ends_with(char::is_whitespace));
// This doesn't end in whitespace at all, so add a newline.
text.push('\n');
}
}
}
#[test]
fn eof_text_ends_with_newline() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline:\n";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
// This should be unchanged!
assert_eq!(text.as_str(), input);
}
#[test]
fn eof_text_ends_with_whitespace() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline: \t";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_ends_with_whitespace_then_newline() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline: \n";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_ends_with_no_whitespace() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "This should be a newline:";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "This should be a newline:\n");
}
#[test]
fn eof_text_is_empty() {
use bumpalo::{collections::String, Bump};
let arena = Bump::new();
let input = "";
let mut text = String::from_str_in(input, &arena);
fmt_text_eof(&mut text);
assert_eq!(text.as_str(), "\n");
}

View file

@ -0,0 +1,407 @@
use crate::annotation::{Formattable, Newlines};
use crate::collection::{fmt_collection, Braces};
use crate::expr::fmt_str_literal;
use crate::spaces::{fmt_default_spaces, fmt_spaces, INDENT};
use crate::Buf;
use roc_parse::ast::{Collection, Module, Spaced};
use roc_parse::header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_parse::ident::UppercaseIdent;
use roc_region::all::Loc;
pub fn fmt_module<'a>(buf: &mut Buf<'_>, module: &'a Module<'a>) {
match module {
Module::Interface { header } => {
fmt_interface_header(buf, header);
}
Module::App { header } => {
fmt_app_header(buf, header);
}
Module::Platform { header } => {
fmt_platform_header(buf, header);
}
Module::Hosted { header } => {
fmt_hosted_header(buf, header);
}
}
}
pub fn fmt_interface_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a InterfaceHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("interface");
// module name
fmt_default_spaces(buf, header.after_interface_keyword, indent);
buf.push_str(header.name.value.as_str());
// exposes
fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent);
buf.push_str("exposes");
fmt_default_spaces(buf, header.after_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
// imports
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
}
pub fn fmt_hosted_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a HostedHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("hosted");
// module name
fmt_default_spaces(buf, header.after_hosted_keyword, indent);
buf.push_str(header.name.value.as_str());
// exposes
fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent);
buf.push_str("exposes");
fmt_default_spaces(buf, header.after_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
// imports
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
// generates
fmt_default_spaces(buf, header.before_generates, indent);
buf.indent(indent);
buf.push_str("generates");
fmt_default_spaces(buf, header.after_generates, indent);
buf.push_str(header.generates.into());
// with
fmt_default_spaces(buf, header.before_with, indent);
buf.indent(indent);
buf.push_str("with");
fmt_default_spaces(buf, header.after_with, indent);
fmt_exposes(buf, header.generates_with, indent);
}
pub fn fmt_app_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a AppHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("app");
fmt_default_spaces(buf, header.after_app_keyword, indent);
fmt_str_literal(buf, header.name.value, indent);
// packages
fmt_default_spaces(buf, header.before_packages, indent);
buf.indent(indent);
buf.push_str("packages");
fmt_default_spaces(buf, header.after_packages, indent);
fmt_packages(buf, header.packages, indent);
// imports
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
// provides
fmt_default_spaces(buf, header.before_provides, indent);
buf.indent(indent);
buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, header.provides_types, indent);
fmt_default_spaces(buf, header.before_to, indent);
buf.indent(indent);
buf.push_str("to");
fmt_default_spaces(buf, header.after_to, indent);
fmt_to(buf, header.to.value, indent);
}
pub fn fmt_platform_header<'a, 'buf>(buf: &mut Buf<'buf>, header: &'a PlatformHeader<'a>) {
let indent = INDENT;
buf.indent(0);
buf.push_str("platform");
fmt_default_spaces(buf, header.after_platform_keyword, indent);
fmt_package_name(buf, header.name.value, indent);
// requires
fmt_default_spaces(buf, header.before_requires, indent);
buf.indent(indent);
buf.push_str("requires");
fmt_default_spaces(buf, header.after_requires, indent);
fmt_requires(buf, &header.requires, indent);
// exposes
fmt_default_spaces(buf, header.before_exposes, indent);
buf.indent(indent);
buf.push_str("exposes");
fmt_default_spaces(buf, header.after_exposes, indent);
fmt_exposes(buf, header.exposes, indent);
// packages
fmt_default_spaces(buf, header.before_packages, indent);
buf.indent(indent);
buf.push_str("packages");
fmt_default_spaces(buf, header.after_packages, indent);
fmt_packages(buf, header.packages, indent);
// imports
fmt_default_spaces(buf, header.before_imports, indent);
buf.indent(indent);
buf.push_str("imports");
fmt_default_spaces(buf, header.after_imports, indent);
fmt_imports(buf, header.imports, indent);
// provides
fmt_default_spaces(buf, header.before_provides, indent);
buf.indent(indent);
buf.push_str("provides");
fmt_default_spaces(buf, header.after_provides, indent);
fmt_provides(buf, header.provides, None, indent);
}
fn fmt_requires<'a, 'buf>(buf: &mut Buf<'buf>, requires: &PlatformRequires<'a>, indent: u16) {
fmt_collection(buf, indent, Braces::Curly, requires.rigids, Newlines::No);
buf.push_str(" {");
buf.spaces(1);
requires.signature.value.format(buf, indent);
buf.push_str(" }");
}
impl<'a> Formattable for TypedIdent<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
buf.indent(indent);
buf.push_str(self.ident.value);
fmt_default_spaces(buf, self.spaces_before_colon, indent);
buf.push_str(":");
buf.spaces(1);
self.ann.value.format(buf, indent);
}
}
fn fmt_package_name<'buf>(buf: &mut Buf<'buf>, name: PackageName, _indent: u16) {
buf.push('"');
buf.push_str_allow_spaces(name.to_str());
buf.push('"');
}
impl<'a, T: Formattable> Formattable for Spaced<'a, T> {
fn is_multiline(&self) -> bool {
use Spaced::*;
match self {
Item(formattable) => formattable.is_multiline(),
SpaceBefore(formattable, spaces) | SpaceAfter(formattable, spaces) => {
!spaces.is_empty() || formattable.is_multiline()
}
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: crate::annotation::Parens,
newlines: Newlines,
indent: u16,
) {
match self {
Spaced::Item(item) => {
item.format_with_options(buf, parens, newlines, indent);
}
Spaced::SpaceBefore(item, spaces) => {
fmt_spaces(buf, spaces.iter(), indent);
item.format_with_options(buf, parens, newlines, indent);
}
Spaced::SpaceAfter(item, spaces) => {
item.format_with_options(buf, parens, newlines, indent);
fmt_spaces(buf, spaces.iter(), indent);
}
}
}
}
fn fmt_imports<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Loc<Spaced<'a, ImportsEntry<'a>>>>,
indent: u16,
) {
fmt_collection(
buf,
indent + INDENT,
Braces::Square,
loc_entries,
Newlines::No,
)
}
fn fmt_provides<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_exposed_names: Collection<'a, Loc<Spaced<'a, ExposedName<'a>>>>,
loc_provided_types: Option<Collection<'a, Loc<Spaced<'a, UppercaseIdent<'a>>>>>,
indent: u16,
) {
fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No);
if let Some(loc_provided) = loc_provided_types {
fmt_default_spaces(buf, &[], indent);
fmt_collection(
buf,
indent + INDENT,
Braces::Curly,
loc_provided,
Newlines::No,
);
}
}
fn fmt_to<'buf>(buf: &mut Buf<'buf>, to: To, indent: u16) {
match to {
To::ExistingPackage(name) => {
buf.push_str(name);
}
To::NewPackage(package_name) => fmt_package_name(buf, package_name, indent),
}
}
fn fmt_exposes<'buf, N: Formattable + Copy>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'_, Loc<Spaced<'_, N>>>,
indent: u16,
) {
fmt_collection(
buf,
indent + INDENT,
Braces::Square,
loc_entries,
Newlines::No,
)
}
pub trait FormatName {
fn format<'buf>(&self, buf: &mut Buf<'buf>);
}
impl<'a> FormatName for &'a str {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self)
}
}
impl<'a> FormatName for ModuleName<'a> {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self.as_str());
}
}
impl<'a> Formattable for ModuleName<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, _indent: u16) {
buf.push_str(self.as_str());
}
}
impl<'a> Formattable for ExposedName<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
buf.indent(indent);
buf.push_str(self.as_str());
}
}
impl<'a> FormatName for ExposedName<'a> {
fn format<'buf>(&self, buf: &mut Buf<'buf>) {
buf.push_str(self.as_str());
}
}
fn fmt_packages<'a, 'buf>(
buf: &mut Buf<'buf>,
loc_entries: Collection<'a, Loc<Spaced<'a, PackageEntry<'a>>>>,
indent: u16,
) {
fmt_collection(buf, indent, Braces::Curly, loc_entries, Newlines::No)
}
impl<'a> Formattable for PackageEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_packages_entry(buf, self, indent);
}
}
impl<'a> Formattable for ImportsEntry<'a> {
fn is_multiline(&self) -> bool {
false
}
fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) {
fmt_imports_entry(buf, self, indent);
}
}
fn fmt_packages_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &PackageEntry<'a>, indent: u16) {
buf.push_str(entry.shorthand);
buf.push(':');
fmt_default_spaces(buf, entry.spaces_after_shorthand, indent);
fmt_package_name(buf, entry.package_name.value, indent);
}
fn fmt_imports_entry<'a, 'buf>(buf: &mut Buf<'buf>, entry: &ImportsEntry<'a>, indent: u16) {
use roc_parse::header::ImportsEntry::*;
buf.indent(indent);
match entry {
Module(module, loc_exposes_entries) => {
buf.push_str(module.as_str());
if !loc_exposes_entries.is_empty() {
buf.push('.');
fmt_collection(
buf,
indent,
Braces::Curly,
*loc_exposes_entries,
Newlines::No,
)
}
}
Package(pkg, name, entries) => {
buf.push_str(pkg);
buf.push('.');
buf.push_str(name.as_str());
if !entries.is_empty() {
buf.push('.');
fmt_collection(buf, indent, Braces::Curly, *entries, Newlines::No)
}
}
}
}

View file

@ -0,0 +1,198 @@
use crate::annotation::{Formattable, Newlines, Parens};
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
use crate::Buf;
use roc_parse::ast::{Base, Pattern};
pub fn fmt_pattern<'a, 'buf>(
buf: &mut Buf<'buf>,
pattern: &'a Pattern<'a>,
indent: u16,
parens: Parens,
) {
pattern.format_with_options(buf, parens, Newlines::No, indent);
}
impl<'a> Formattable for Pattern<'a> {
fn is_multiline(&self) -> bool {
// Theory: a pattern should only be multiline when it contains a comment
match self {
Pattern::SpaceBefore(_, spaces) | Pattern::SpaceAfter(_, spaces) => {
debug_assert!(!spaces.is_empty());
spaces.iter().any(|s| s.is_comment())
}
Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()),
Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(),
Pattern::OptionalField(_, expr) => expr.is_multiline(),
Pattern::Identifier(_)
| Pattern::Tag(_)
| Pattern::OpaqueRef(_)
| Pattern::Apply(_, _)
| Pattern::NumLiteral(..)
| Pattern::NonBase10Literal { .. }
| Pattern::FloatLiteral(..)
| Pattern::StrLiteral(_)
| Pattern::SingleQuote(_)
| Pattern::Underscore(_)
| Pattern::Malformed(_)
| Pattern::MalformedIdent(_, _)
| Pattern::QualifiedIdentifier { .. } => false,
}
}
fn format_with_options<'buf>(
&self,
buf: &mut Buf<'buf>,
parens: Parens,
newlines: Newlines,
indent: u16,
) {
use self::Pattern::*;
match self {
Identifier(string) => {
buf.indent(indent);
buf.push_str(string)
}
Tag(name) | OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}
Apply(loc_pattern, loc_arg_patterns) => {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !loc_arg_patterns.is_empty() && parens == Parens::InApply;
if parens {
buf.push('(');
}
loc_pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent);
for loc_arg in loc_arg_patterns.iter() {
buf.spaces(1);
loc_arg.format_with_options(buf, Parens::InApply, Newlines::No, indent);
}
if parens {
buf.push(')');
}
}
RecordDestructure(loc_patterns) => {
buf.indent(indent);
buf.push_str("{");
if !loc_patterns.is_empty() {
buf.spaces(1);
let mut it = loc_patterns.iter().peekable();
while let Some(loc_pattern) = it.next() {
loc_pattern.format(buf, indent);
if it.peek().is_some() {
buf.push_str(",");
buf.spaces(1);
}
}
buf.spaces(1);
}
buf.push_str("}");
}
RequiredField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(":");
buf.spaces(1);
loc_pattern.format(buf, indent);
}
OptionalField(name, loc_pattern) => {
buf.indent(indent);
buf.push_str(name);
buf.push_str(" ?");
buf.spaces(1);
loc_pattern.format(buf, indent);
}
&NumLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
&NonBase10Literal {
base,
string,
is_negative,
} => {
buf.indent(indent);
if is_negative {
buf.push('-');
}
match base {
Base::Hex => buf.push_str("0x"),
Base::Octal => buf.push_str("0o"),
Base::Binary => buf.push_str("0b"),
Base::Decimal => { /* nothing */ }
}
buf.push_str(string);
}
&FloatLiteral(string) => {
buf.indent(indent);
buf.push_str(string);
}
StrLiteral(literal) => {
todo!("Format string literal: {:?}", literal);
}
SingleQuote(string) => {
buf.push('\'');
buf.push_str(string);
buf.push('\'');
}
Underscore(name) => {
buf.indent(indent);
buf.push('_');
buf.push_str(name);
}
// Space
SpaceBefore(sub_pattern, spaces) => {
if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
sub_pattern.format_with_options(buf, parens, newlines, indent);
}
SpaceAfter(sub_pattern, spaces) => {
sub_pattern.format_with_options(buf, parens, newlines, indent);
// if only_comments {
if !sub_pattern.is_multiline() {
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
}
// Malformed
Malformed(string) | MalformedIdent(string, _) => {
buf.indent(indent);
buf.push_str(string);
}
QualifiedIdentifier { module_name, ident } => {
buf.indent(indent);
if !module_name.is_empty() {
buf.push_str(module_name);
buf.push('.');
}
buf.push_str(ident);
}
}
}
}

View file

@ -0,0 +1,786 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::{
ast::{
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, Has,
HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef,
TypeHeader, ValueDef, WhenBranch,
},
header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
},
ident::UppercaseIdent,
};
use roc_region::all::{Loc, Region};
use crate::{Ast, Buf};
/// The number of spaces to indent.
pub const INDENT: u16 = 4;
pub fn fmt_default_spaces<'a, 'buf>(
buf: &mut Buf<'buf>,
spaces: &[CommentOrNewline<'a>],
indent: u16,
) {
if spaces.is_empty() {
buf.spaces(1);
} else {
fmt_spaces(buf, spaces.iter(), indent);
}
}
pub fn fmt_spaces<'a, 'buf, I>(buf: &mut Buf<'buf>, spaces: I, indent: u16)
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
// Only ever print two newlines back to back.
// (Two newlines renders as one blank line.)
let mut consecutive_newlines = 0;
let mut encountered_comment = false;
for space in spaces {
match space {
Newline => {
if !encountered_comment && (consecutive_newlines < 2) {
buf.newline();
// Don't bother incrementing it if we're already over the limit.
// There's no upside, and it might eventually overflow,
consecutive_newlines += 1;
}
}
LineComment(comment) => {
buf.indent(indent);
fmt_comment(buf, comment);
buf.newline();
encountered_comment = true;
}
DocComment(docs) => {
buf.indent(indent);
fmt_docs(buf, docs);
buf.newline();
encountered_comment = true;
}
}
}
}
#[derive(Eq, PartialEq, Debug)]
pub enum NewlineAt {
Top,
Bottom,
Both,
None,
}
/// Like format_spaces, but remove newlines and keep only comments.
/// The `new_line_at` argument describes how new lines should be inserted
/// at the beginning or at the end of the block
/// in the case of there is some comment in the `spaces` argument.
pub fn fmt_comments_only<'a, 'buf, I>(
buf: &mut Buf<'buf>,
spaces: I,
new_line_at: NewlineAt,
indent: u16,
) where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
use self::CommentOrNewline::*;
use NewlineAt::*;
let mut comment_seen = false;
for space in spaces {
match space {
Newline => {}
LineComment(comment) => {
if comment_seen || new_line_at == Top || new_line_at == Both {
buf.newline();
}
buf.indent(indent);
fmt_comment(buf, comment);
comment_seen = true;
}
DocComment(docs) => {
if comment_seen || new_line_at == Top || new_line_at == Both {
buf.newline();
}
buf.indent(indent);
fmt_docs(buf, docs);
comment_seen = true;
}
}
}
if comment_seen && (new_line_at == Bottom || new_line_at == Both) {
buf.newline();
}
}
fn fmt_comment<'buf>(buf: &mut Buf<'buf>, comment: &str) {
buf.push('#');
if !comment.starts_with(' ') {
buf.spaces(1);
}
buf.push_str(comment.trim_end());
}
pub fn count_leading_newlines<'a, I>(data: I) -> u16
where
I: Iterator<Item = &'a CommentOrNewline<'a>>,
{
let mut count = 0;
let mut allow_counting = false;
for (index, val) in data.enumerate() {
let is_first = index == 0;
let is_newline = matches!(val, CommentOrNewline::Newline);
if is_first && is_newline {
allow_counting = true
}
if is_newline && allow_counting {
count += 1;
} else {
break;
}
}
count
}
fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
buf.push_str("##");
if !docs.is_empty() {
buf.spaces(1);
}
buf.push_str(docs.trim_end());
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
pub trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = self.defs.clone();
for type_def in defs.type_defs.iter_mut() {
*type_def = type_def.remove_spaces(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.remove_spaces(arena);
}
for region_def in defs.regions.iter_mut() {
*region_def = region_def.remove_spaces(arena);
}
defs
},
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {
Module::Interface { header } => Module::Interface {
header: InterfaceHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_header: &[],
after_interface_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
},
},
Module::App { header } => Module::App {
header: AppHeader {
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
},
},
Module::Platform { header } => Module::Platform {
header: PlatformHeader {
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
},
},
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
}
}
}
impl<'a> RemoveSpaces<'a> for Region {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Region::zero()
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
derived,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
derived: derived.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for Def<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Has<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Has::Has
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Defs(a, b) => {
let mut defs = a.clone();
defs.space_before = vec![Default::default(); defs.len()];
defs.space_after = vec![Default::default(); defs.len()];
defs.regions = vec![Region::zero(); defs.len()];
defs.spaces.clear();
for type_def in defs.type_defs.iter_mut() {
*type_def = type_def.remove_spaces(arena);
}
for value_def in defs.value_defs.iter_mut() {
*value_def = value_def.remove_spaces(arena);
}
Expr::Defs(arena.alloc(defs), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, TypeHeader { name, vars }) => TypeAnnotation::As(
arena.alloc(a.remove_spaces(arena)),
&[],
TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
),
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
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),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Apply { name, args } => Tag::Apply {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
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)
}
}
}
}