use crate::annotation::{Formattable, Newlines, Parens}; use crate::pattern::fmt_pattern; use crate::spaces::{fmt_default_newline, fmt_spaces, INDENT}; use crate::Buf; use roc_parse::ast::{ AbilityMember, Defs, Expr, ExtractSpaces, Pattern, Spaces, StrLiteral, 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( &self, buf: &mut Buf<'_>, _parens: Parens, _newlines: Newlines, indent: u16, ) { let mut prev_spaces = true; 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()]; if prev_spaces { fmt_spaces(buf, spaces_before.iter(), indent); } else { fmt_default_newline(buf, spaces_before, 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); prev_spaces = !spaces_after.is_empty(); } } } 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( &self, buf: &mut 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); let need_parens = matches!(var.value, Pattern::Apply(..)); if need_parens { buf.push_str("("); } fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); buf.indent(indent); if need_parens { buf.push_str(")"); } } buf.push_str(" :"); buf.spaces(1); ann.format(buf, indent) } Opaque { header, typ: ann, derived: has_abilities, } => { let ann_is_where_clause = matches!(ann.extract_spaces().item, TypeAnnotation::Where(..)); // Always put the has-abilities clause on a newline if the opaque annotation // contains a where-has clause. let has_abilities_multiline = if let Some(has_abilities) = has_abilities { !has_abilities.value.is_empty() && ann_is_where_clause } else { false }; let make_multiline = ann.is_multiline() || has_abilities_multiline; fmt_general_def(header, buf, indent, ":=", &ann.value, newlines); if let Some(has_abilities) = has_abilities { buf.spaces(1); has_abilities.format_with_options( buf, Parens::NotNeeded, Newlines::from_bool(make_multiline), indent + 1 + 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.indent(indent); } buf.push_str(" has"); if !self.is_multiline() { debug_assert_eq!(members.len(), 1); buf.spaces(1); members[0].format_with_options( buf, Parens::NotNeeded, Newlines::No, indent + INDENT, ); } else { for member in members.iter() { member.format_with_options( buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT, ); } } } } } } impl<'a> Formattable for TypeHeader<'a> { fn is_multiline(&self) -> bool { self.vars.iter().any(|v| v.is_multiline()) } fn format_with_options( &self, buf: &mut Buf<'_>, _parens: Parens, _newlines: Newlines, indent: u16, ) { buf.indent(indent); buf.push_str(self.name.value); for var in self.vars.iter() { buf.spaces(1); fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); 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 { condition, .. } => condition.is_multiline(), ExpectFx { condition, .. } => condition.is_multiline(), Dbg { condition, .. } => condition.is_multiline(), } } fn format_with_options( &self, buf: &mut Buf<'_>, _parens: Parens, newlines: Newlines, indent: u16, ) { use roc_parse::ast::ValueDef::*; match self { Annotation(loc_pattern, loc_annotation) => { fmt_general_def( loc_pattern, buf, indent, ":", &loc_annotation.value, newlines, ); } Body(loc_pattern, loc_expr) => { fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent); } Dbg { condition, .. } => fmt_dbg_in_def(buf, condition, self.is_multiline(), indent), Expect { condition, .. } => fmt_expect(buf, condition, self.is_multiline(), indent), ExpectFx { condition, .. } => { fmt_expect_fx(buf, condition, self.is_multiline(), indent) } AnnotatedBody { ann_pattern, ann_type, comment, body_pattern, body_expr, } => { fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines); 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); } } } } fn fmt_general_def( lhs: L, buf: &mut Buf, indent: u16, sep: &str, rhs: &TypeAnnotation, newlines: Newlines, ) { lhs.format(buf, indent); buf.indent(indent); if rhs.is_multiline() { buf.spaces(1); buf.push_str(sep); buf.spaces(1); let should_outdent = should_outdent(rhs); if should_outdent { match rhs { TypeAnnotation::SpaceBefore(sub_def, _) => { sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); } _ => { rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); } } } else { rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT); } } else { buf.spaces(1); buf.push_str(sep); buf.spaces(1); rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); } } fn should_outdent(mut rhs: &TypeAnnotation) -> bool { loop { match rhs { TypeAnnotation::SpaceBefore(sub_def, spaces) => { let is_only_newlines = spaces.iter().all(|s| s.is_newline()); if !is_only_newlines || !sub_def.is_multiline() { return false; } rhs = sub_def; } TypeAnnotation::Where(ann, _clauses) => { if !ann.is_multiline() { return false; } rhs = &ann.value; } TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true, _ => return false, } } } fn fmt_dbg_in_def<'a>( buf: &mut Buf<'_>, condition: &'a Loc>, is_multiline: bool, indent: u16, ) { buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("dbg"); let return_indent = if is_multiline { buf.newline(); indent + INDENT } else { buf.spaces(1); indent }; condition.format(buf, return_indent); } fn fmt_expect<'a>( buf: &mut Buf<'_>, condition: &'a Loc>, is_multiline: bool, indent: u16, ) { buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("expect"); let return_indent = if is_multiline { buf.newline(); indent + INDENT } else { buf.spaces(1); indent }; condition.format(buf, return_indent); } fn fmt_expect_fx<'a>( buf: &mut Buf<'_>, condition: &'a Loc>, is_multiline: bool, indent: u16, ) { buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("expect-fx"); let return_indent = if is_multiline { buf.newline(); indent + INDENT } else { buf.spaces(1); indent }; condition.format(buf, return_indent); } pub fn fmt_value_def(buf: &mut Buf<'_>, def: &roc_parse::ast::ValueDef<'_>, indent: u16) { def.format(buf, indent); } pub fn fmt_type_def(buf: &mut Buf<'_>, def: &roc_parse::ast::TypeDef<'_>, indent: u16) { def.format(buf, indent); } pub fn fmt_defs(buf: &mut Buf<'_>, defs: &Defs<'_>, indent: u16) { defs.format(buf, indent); } pub fn fmt_body<'a>(buf: &mut Buf<'_>, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, indent: u16) { pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent); buf.indent(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, ); } } Expr::Defs(..) | Expr::BinOps(_, _) | Expr::Backpassing(..) => { // Binop chains always get a newline. Otherwise you can have things like: // // something = foo // |> bar baz // // By always inserting a newline, this becomes: // // something = // foo // |> bar baz // // This makes it clear what the binop is applying to! buf.newline(); body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); } Expr::When(..) | Expr::Str(StrLiteral::Block(_)) => { buf.ensure_ends_with_newline(); 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_with_options( &self, buf: &mut Buf<'_>, _parens: Parens, _newlines: Newlines, indent: u16, ) { let Spaces { before, item, .. } = self.name.value.extract_spaces(); fmt_spaces(buf, before.iter(), indent); buf.indent(indent); buf.push_str(item); buf.spaces(1); buf.push(':'); buf.spaces(1); self.typ.value.format(buf, indent + INDENT); } }