From a2083cec30ada7e64b83d25758c5e9811e1851a3 Mon Sep 17 00:00:00 2001 From: Anthony Bullard Date: Wed, 20 Nov 2024 09:00:57 -0600 Subject: [PATCH] Parsing support for snake_case identifiers In this initial commit, I have done the following: - Added unit tests to roc_parse's ident.rs file to cover at least the simplest Ident enum cases (Tag, OpaqueRef, and simple Access) - Added '_' as a valid "rest" character in both uppercase and lowercase identifier parts - Updated the test_syntax snapshots appropriately There is still a lot left to do here. Such as: - Do we want to allow multiple '_'s to parse successfully? - Handle qualified access - Handle accessor functions - Handle record update functions - Remove the UnderscoreInMiddle case from BadIdent - Write unit tests for Malformed Idents I am not a "Rustacean" by any means, but have been through the Book in years past. Any feedback on the way I wrote the tests or any other part of the implementation would be very appreciated. --- crates/cli/src/format.rs | 40 ++- crates/cli/src/lib.rs | 8 + crates/cli/src/main.rs | 14 +- crates/compiler/can/src/pattern.rs | 8 + crates/compiler/fmt/src/annotation.rs | 251 ++++++++++++---- crates/compiler/fmt/src/collection.rs | 5 +- crates/compiler/fmt/src/def.rs | 272 ++++++++++++++---- crates/compiler/fmt/src/expr.rs | 258 +++++++++++------ crates/compiler/fmt/src/header.rs | 192 +++++++++---- crates/compiler/fmt/src/pattern.rs | 146 ++++++++-- crates/compiler/load/tests/test_reporting.rs | 37 +-- crates/compiler/parse/src/ident.rs | 172 ++++++++++- crates/compiler/parse/src/normalize.rs | 1 + crates/compiler/problem/src/can.rs | 4 +- .../specialize_types/src/specialize_type.rs | 11 +- .../compiler/test_syntax/src/test_helpers.rs | 13 +- ...ed_ident_due_to_underscore.expr.result-ast | 13 - .../underscore_expr_in_def.expr.result-ast | 42 --- ...ith_underscore_closure.expr.formatted.roc} | 0 ...rg_with_underscore_closure.expr.result-ast | 15 + ...ingle_arg_with_underscore_closure.expr.roc | 1 + .../underscore_expr_in_def.expr.formatted.roc | 0 .../underscore_expr_in_def.expr.result-ast | 45 +++ .../underscore_expr_in_def.expr.roc | 2 +- crates/compiler/test_syntax/tests/test_fmt.rs | 72 ++--- .../test_syntax/tests/test_snapshots.rs | 4 +- .../src/analysis/analysed_doc.rs | 4 +- .../language_server/src/analysis/parse_ast.rs | 8 +- crates/repl_eval/src/gen.rs | 3 +- crates/reporting/src/error/canonicalize.rs | 28 +- crates/reporting/src/error/expect.rs | 5 +- 31 files changed, 1214 insertions(+), 460 deletions(-) delete mode 100644 crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.result-ast delete mode 100644 crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast rename crates/compiler/test_syntax/tests/snapshots/{malformed/malformed_ident_due_to_underscore.expr.roc => pass/single_arg_with_underscore_closure.expr.formatted.roc} (100%) create mode 100644 crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.result-ast create mode 100644 crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.roc rename crates/compiler/test_syntax/tests/snapshots/{malformed => pass}/underscore_expr_in_def.expr.formatted.roc (100%) create mode 100644 crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.result-ast rename crates/compiler/test_syntax/tests/snapshots/{malformed => pass}/underscore_expr_in_def.expr.roc (50%) diff --git a/crates/cli/src/format.rs b/crates/cli/src/format.rs index 8b4f731956..e068e0fcce 100644 --- a/crates/cli/src/format.rs +++ b/crates/cli/src/format.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use bumpalo::Bump; use roc_error_macros::{internal_error, user_error}; +use roc_fmt::annotation::MigrationFlags; use roc_fmt::def::fmt_defs; use roc_fmt::header::fmt_header; use roc_fmt::Buf; @@ -63,14 +64,18 @@ fn is_roc_file(path: &Path) -> bool { matches!(path.extension().and_then(OsStr::to_str), Some("roc")) } -pub fn format_files(files: std::vec::Vec, mode: FormatMode) -> Result<(), String> { +pub fn format_files( + files: std::vec::Vec, + mode: FormatMode, + flags: &MigrationFlags, +) -> Result<(), String> { let arena = Bump::new(); let mut files_to_reformat = Vec::new(); // to track which files failed `roc format --check` for file in flatten_directories(files) { let src = std::fs::read_to_string(&file).unwrap(); - match format_src(&arena, &src) { + match format_src(&arena, &src, flags) { Ok(buf) => { match mode { FormatMode::CheckOnly => { @@ -183,12 +188,16 @@ pub enum FormatProblem { }, } -pub fn format_src(arena: &Bump, src: &str) -> Result { +pub fn format_src( + arena: &Bump, + src: &str, + flags: &MigrationFlags, +) -> Result { let ast = arena.alloc(parse_all(arena, src).unwrap_or_else(|e| { user_error!("Unexpected parse failure when parsing this formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, e) })); let mut buf = Buf::new_in(arena); - fmt_all(&mut buf, ast); + fmt_all(&mut buf, ast, flags); let reparsed_ast = match arena.alloc(parse_all(arena, buf.as_str())) { Ok(ast) => ast, @@ -208,7 +217,9 @@ pub fn format_src(arena: &Bump, src: &str) -> Result { // the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same. // I don't have the patience to debug this right now, so let's leave it for another day... // TODO: fix PartialEq impl on ast types - if format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}") { + if !flags.at_least_one_active() + && format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}") + { return Err(FormatProblem::ReformattingChangedAst { formatted_src: buf.as_str().to_string(), ast_before: format!("{ast_normalized:#?}\n"), @@ -219,7 +230,7 @@ pub fn format_src(arena: &Bump, src: &str) -> Result { // Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted let mut reformatted_buf = Buf::new_in(arena); - fmt_all(&mut reformatted_buf, reparsed_ast); + fmt_all(&mut reformatted_buf, reparsed_ast, flags); if buf.as_str() != reformatted_buf.as_str() { return Err(FormatProblem::ReformattingUnstable { @@ -248,10 +259,10 @@ fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result, SyntaxErr }) } -fn fmt_all<'a>(buf: &mut Buf<'a>, ast: &'a FullAst) { - fmt_header(buf, &ast.header); +fn fmt_all<'a>(buf: &mut Buf<'a>, ast: &'a FullAst, flags: &MigrationFlags) { + fmt_header(buf, &ast.header, flags); - fmt_defs(buf, &ast.defs, 0); + fmt_defs(buf, &ast.defs, flags, 0); buf.fmt_end_of_file(); } @@ -298,8 +309,9 @@ main = fn test_single_file_needs_reformatting() { let dir = tempdir().unwrap(); let file_path = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC); + let flags = MigrationFlags::new(false); - let result = format_files(vec![file_path.clone()], FormatMode::CheckOnly); + let result = format_files(vec![file_path.clone()], FormatMode::CheckOnly, &flags); assert!(result.is_err()); assert_eq!( result.unwrap_err(), @@ -317,8 +329,9 @@ main = let dir = tempdir().unwrap(); let file1 = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC); let file2 = setup_test_file(dir.path(), "test2.roc", UNFORMATTED_ROC); + let flags = MigrationFlags::new(false); - let result = format_files(vec![file1, file2], FormatMode::CheckOnly); + let result = format_files(vec![file1, file2], FormatMode::CheckOnly, &flags); assert!(result.is_err()); let error_message = result.unwrap_err(); assert!(error_message.contains("test1.roc") && error_message.contains("test2.roc")); @@ -330,8 +343,9 @@ main = fn test_no_files_need_reformatting() { let dir = tempdir().unwrap(); let file_path = setup_test_file(dir.path(), "formatted.roc", FORMATTED_ROC); + let flags = MigrationFlags::new(false); - let result = format_files(vec![file_path], FormatMode::CheckOnly); + let result = format_files(vec![file_path], FormatMode::CheckOnly, &flags); assert!(result.is_ok()); cleanup_temp_dir(dir); @@ -343,10 +357,12 @@ main = let file_formatted = setup_test_file(dir.path(), "formatted.roc", FORMATTED_ROC); let file1_unformated = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC); let file2_unformated = setup_test_file(dir.path(), "test2.roc", UNFORMATTED_ROC); + let flags = MigrationFlags::new(false); let result = format_files( vec![file_formatted, file1_unformated, file2_unformated], FormatMode::CheckOnly, + &flags, ); assert!(result.is_err()); let error_message = result.unwrap_err(); diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index a31e053b98..698e982c0c 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -89,6 +89,7 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP"; pub const FLAG_PP_HOST: &str = "host"; pub const FLAG_PP_PLATFORM: &str = "platform"; pub const FLAG_PP_DYLIB: &str = "lib"; +pub const FLAG_MIGRATE: &str = "migrate"; pub const VERSION: &str = env!("ROC_VERSION"); const DEFAULT_GENERATED_DOCS_DIR: &str = "generated-docs"; @@ -344,6 +345,13 @@ pub fn build_app() -> Command { .action(ArgAction::SetTrue) .required(false), ) + .arg( + Arg::new(FLAG_MIGRATE) + .long(FLAG_MIGRATE) + .help("Will fixup syntax to match the latest preferred style. This can cause changes to variable names and more.") + .action(ArgAction::SetTrue) + .required(false), + ) .arg( Arg::new(FLAG_STDIN) .long(FLAG_STDIN) diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 17a9b5b43a..f6c13f48a2 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -5,12 +5,14 @@ use roc_build::program::{check_file, CodeGenBackend}; use roc_cli::{ build_app, format_files, format_src, test, BuildConfig, FormatMode, CMD_BUILD, CMD_CHECK, CMD_DEV, CMD_DOCS, CMD_FORMAT, CMD_GLUE, CMD_PREPROCESS_HOST, CMD_REPL, CMD_RUN, CMD_TEST, - CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_LIB, FLAG_MAIN, FLAG_NO_COLOR, - FLAG_NO_HEADER, FLAG_NO_LINK, FLAG_OUTPUT, FLAG_PP_DYLIB, FLAG_PP_HOST, FLAG_PP_PLATFORM, - FLAG_STDIN, FLAG_STDOUT, FLAG_TARGET, FLAG_TIME, GLUE_DIR, GLUE_SPEC, ROC_FILE, VERSION, + CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_DEV, FLAG_LIB, FLAG_MAIN, FLAG_MIGRATE, + FLAG_NO_COLOR, FLAG_NO_HEADER, FLAG_NO_LINK, FLAG_OUTPUT, FLAG_PP_DYLIB, FLAG_PP_HOST, + FLAG_PP_PLATFORM, FLAG_STDIN, FLAG_STDOUT, FLAG_TARGET, FLAG_TIME, GLUE_DIR, GLUE_SPEC, + ROC_FILE, VERSION, }; use roc_docs::generate_docs_html; use roc_error_macros::user_error; +use roc_fmt::annotation::MigrationFlags; use roc_gen_dev::AssemblyBackendMode; use roc_gen_llvm::llvm::build::LlvmBackendMode; use roc_load::{LoadingProblem, Threading}; @@ -310,6 +312,7 @@ fn main() -> io::Result<()> { Some((CMD_FORMAT, matches)) => { let from_stdin = matches.get_flag(FLAG_STDIN); let to_stdout = matches.get_flag(FLAG_STDOUT); + let migrate = matches.get_flag(FLAG_MIGRATE); let format_mode = if to_stdout { FormatMode::WriteToStdout } else { @@ -318,6 +321,7 @@ fn main() -> io::Result<()> { false => FormatMode::WriteToFile, } }; + let flags = MigrationFlags::new(migrate); if from_stdin && matches!(format_mode, FormatMode::WriteToFile) { eprintln!("When using the --stdin flag, either the --check or the --stdout flag must also be specified. (Otherwise, it's unclear what filename to write to!)"); @@ -370,7 +374,7 @@ fn main() -> io::Result<()> { std::process::exit(1); }); - match format_src(&arena, src) { + match format_src(&arena, src, &flags) { Ok(formatted_src) => { match format_mode { FormatMode::CheckOnly => { @@ -402,7 +406,7 @@ fn main() -> io::Result<()> { } } } else { - match format_files(roc_files, format_mode) { + match format_files(roc_files, format_mode, &flags) { Ok(()) => 0, Err(message) => { eprintln!("{message}"); diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs index 0cf1ece726..6e3b997d3e 100644 --- a/crates/compiler/can/src/pattern.rs +++ b/crates/compiler/can/src/pattern.rs @@ -273,6 +273,14 @@ pub fn canonicalize_def_header_pattern<'a>( region, ) { Ok((symbol, shadowing_ability_member)) => { + if name.contains("__") { + env.problem(Problem::RuntimeError(RuntimeError::MalformedPattern( + MalformedPatternProblem::BadIdent( + roc_parse::ident::BadIdent::TooManyUnderscores(region.start()), + ), + region, + ))); + } let can_pattern = match shadowing_ability_member { // A fresh identifier. None => { diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs index b95552009f..0ab69c787a 100644 --- a/crates/compiler/fmt/src/annotation.rs +++ b/crates/compiler/fmt/src/annotation.rs @@ -67,13 +67,34 @@ impl Newlines { } } +pub struct MigrationFlags { + pub(crate) snakify: bool, +} + +impl MigrationFlags { + pub fn new(snakify: bool) -> Self { + MigrationFlags { snakify } + } + + pub fn at_least_one_active(&self) -> bool { + self.snakify + } +} + pub trait Formattable { fn is_multiline(&self) -> bool; - fn format_with_options(&self, buf: &mut Buf, _parens: Parens, _newlines: Newlines, indent: u16); + fn format_with_options( + &self, + buf: &mut Buf, + _parens: Parens, + _newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ); - fn format(&self, buf: &mut Buf, indent: u16) { - self.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + fn format(&self, buf: &mut Buf, flags: &MigrationFlags, indent: u16) { + self.format_with_options(buf, Parens::NotNeeded, Newlines::No, flags, indent); } } @@ -86,12 +107,19 @@ where (*self).is_multiline() } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { - (*self).format_with_options(buf, parens, newlines, indent) + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { + (*self).format_with_options(buf, parens, newlines, flags, indent) } - fn format(&self, buf: &mut Buf, indent: u16) { - (*self).format(buf, indent) + fn format(&self, buf: &mut Buf, flags: &MigrationFlags, indent: u16) { + (*self).format(buf, flags, indent) } } @@ -113,13 +141,20 @@ where self.value.is_multiline() } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { self.value - .format_with_options(buf, parens, newlines, indent) + .format_with_options(buf, parens, newlines, flags, indent) } - fn format(&self, buf: &mut Buf, indent: u16) { - self.value.format(buf, indent) + fn format(&self, buf: &mut Buf, flags: &MigrationFlags, indent: u16) { + self.value.format(buf, flags, indent) } } @@ -133,6 +168,7 @@ impl<'a> Formattable for UppercaseIdent<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + _flags: &MigrationFlags, _indent: u16, ) { buf.push_str((*self).into()) @@ -195,14 +231,22 @@ impl<'a> Formattable for TypeAnnotation<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { - fmt_ty_ann(self, buf, indent, parens, newlines, false); + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { + fmt_ty_ann(self, buf, flags, indent, parens, newlines, false); } } fn fmt_ty_ann( me: &TypeAnnotation<'_>, buf: &mut Buf<'_>, + flags: &MigrationFlags, indent: u16, parens: Parens, newlines: Newlines, @@ -248,6 +292,7 @@ fn fmt_ty_ann( fmt_ty_ann( &argument.value, buf, + flags, indent, Parens::InFunctionType, Newlines::Yes, @@ -270,7 +315,7 @@ fn fmt_ty_ann( buf.spaces(1); ret.value - .format_with_options(buf, Parens::InFunctionType, Newlines::No, indent); + .format_with_options(buf, Parens::InFunctionType, Newlines::No, flags, indent); if needs_parens { buf.push(')') @@ -312,12 +357,17 @@ fn fmt_ty_ann( let arg = arg.extract_spaces(); fmt_spaces(buf, arg.before.iter(), arg_indent); buf.ensure_ends_with_newline(); - arg.item - .format_with_options(buf, Parens::InApply, Newlines::Yes, arg_indent); + arg.item.format_with_options( + buf, + Parens::InApply, + Newlines::Yes, + flags, + arg_indent, + ); fmt_spaces(buf, arg.after.iter(), arg_indent); } else { buf.spaces(1); - arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent); + arg.format_with_options(buf, Parens::InApply, Newlines::No, flags, arg_indent); } } @@ -339,24 +389,24 @@ fn fmt_ty_ann( } TypeAnnotation::TagUnion { tags, ext } => { - fmt_collection(buf, indent, Braces::Square, *tags, newlines); - fmt_ext(ext, buf, indent); + fmt_collection(buf, flags, indent, Braces::Square, *tags, newlines); + fmt_ext(ext, buf, flags, indent); } TypeAnnotation::Tuple { elems: fields, ext } => { - fmt_ty_collection(buf, indent, Braces::Round, *fields, newlines); - fmt_ext(ext, buf, indent); + fmt_ty_collection(buf, flags, indent, Braces::Round, *fields, newlines); + fmt_ext(ext, buf, flags, indent); } TypeAnnotation::Record { fields, ext } => { - fmt_collection(buf, indent, Braces::Curly, *fields, newlines); - fmt_ext(ext, buf, indent); + fmt_collection(buf, flags, indent, Braces::Curly, *fields, newlines); + fmt_ext(ext, buf, flags, indent); } TypeAnnotation::As(lhs, _spaces, TypeHeader { name, vars }) => { // TODO use _spaces? lhs.value - .format_with_options(buf, Parens::InFunctionType, Newlines::No, indent); + .format_with_options(buf, Parens::InFunctionType, Newlines::No, flags, indent); buf.spaces(1); buf.push_str("as"); buf.spaces(1); @@ -364,12 +414,12 @@ fn fmt_ty_ann( for var in *vars { buf.spaces(1); var.value - .format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + .format_with_options(buf, Parens::NotNeeded, Newlines::No, flags, indent); } } TypeAnnotation::Where(annot, implements_clauses) => { - annot.format_with_options(buf, parens, newlines, indent); + annot.format_with_options(buf, parens, newlines, flags, indent); if implements_clauses .iter() .any(|implements| implements.is_multiline()) @@ -386,7 +436,7 @@ fn fmt_ty_ann( "," }); buf.spaces(1); - has.format_with_options(buf, parens, newlines, indent); + has.format_with_options(buf, parens, newlines, flags, indent); } } TypeAnnotation::Malformed(raw) => { @@ -423,6 +473,7 @@ fn lower<'a, 'b: 'a>( fn fmt_ty_collection( buf: &mut Buf<'_>, + flags: &MigrationFlags, indent: u16, braces: Braces, items: Collection<'_, Loc>>, @@ -459,10 +510,15 @@ fn fmt_ty_collection( let new_items = Collection::with_items_and_comments(arena, new_items.into_bump_slice(), final_comments); - fmt_collection(buf, indent, braces, new_items, newlines) + fmt_collection(buf, flags, indent, braces, new_items, newlines) } -fn fmt_ext(ext: &Option<&Loc>>, buf: &mut Buf<'_>, indent: u16) { +fn fmt_ext( + ext: &Option<&Loc>>, + buf: &mut Buf<'_>, + flags: &MigrationFlags, + indent: u16, +) { if let Some(loc_ext_ann) = *ext { let me = ann_lift_spaces(buf.text.bump(), &loc_ext_ann.value); let parens_needed = !me.before.is_empty() || ext_needs_parens(me.item); @@ -470,11 +526,11 @@ fn fmt_ext(ext: &Option<&Loc>>, buf: &mut Buf<'_>, indent: u1 // We need to make sure to not have whitespace before the ext of a type, // since that would make it parse as something else. buf.push('('); - loc_ext_ann.value.format(buf, indent + INDENT); + loc_ext_ann.value.format(buf, flags, indent + INDENT); buf.indent(indent); buf.push(')'); } else { - loc_ext_ann.value.format(buf, indent + INDENT); + loc_ext_ann.value.format(buf, flags, indent + INDENT); } } } @@ -530,9 +586,16 @@ impl<'a> Formattable for AssignedField<'a, TypeAnnotation<'a>> { is_multiline_assigned_field_help(self) } - fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + _parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { // we abuse the `Newlines` type to decide between multiline or single-line layout - format_assigned_field_help(self, buf, indent, 1, newlines == Newlines::Yes); + format_assigned_field_help(self, buf, flags, indent, 1, newlines == Newlines::Yes); } } @@ -541,9 +604,16 @@ impl<'a> Formattable for AssignedField<'a, Expr<'a>> { is_multiline_assigned_field_help(self) } - fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + _parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { // we abuse the `Newlines` type to decide between multiline or single-line layout - format_assigned_field_help(self, buf, indent, 0, newlines == Newlines::Yes); + format_assigned_field_help(self, buf, flags, indent, 0, newlines == Newlines::Yes); } } @@ -562,6 +632,7 @@ fn is_multiline_assigned_field_help(afield: &AssignedField<'_, T fn format_assigned_field_help( zelf: &AssignedField, buf: &mut Buf, + flags: &MigrationFlags, indent: u16, separator_spaces: usize, is_multiline: bool, @@ -587,7 +658,7 @@ fn format_assigned_field_help( buf.indent(indent); buf.push(':'); buf.spaces(1); - ann.value.format(buf, indent); + ann.value.format(buf, flags, indent); } OptionalValue(name, spaces, ann) => { if is_multiline { @@ -605,7 +676,7 @@ fn format_assigned_field_help( buf.indent(indent); buf.push('?'); buf.spaces(1); - ann.value.format(buf, indent); + ann.value.format(buf, flags, indent); } IgnoredValue(name, spaces, ann) => { if is_multiline { @@ -623,7 +694,7 @@ fn format_assigned_field_help( buf.spaces(separator_spaces); buf.push(':'); buf.spaces(1); - ann.value.format(buf, indent); + ann.value.format(buf, flags, indent); } LabelOnly(name) => { if is_multiline { @@ -635,10 +706,24 @@ fn format_assigned_field_help( } AssignedField::SpaceBefore(sub_field, spaces) => { fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - format_assigned_field_help(sub_field, buf, indent, separator_spaces, is_multiline); + format_assigned_field_help( + sub_field, + buf, + flags, + indent, + separator_spaces, + is_multiline, + ); } AssignedField::SpaceAfter(sub_field, spaces) => { - format_assigned_field_help(sub_field, buf, indent, separator_spaces, is_multiline); + format_assigned_field_help( + sub_field, + buf, + flags, + indent, + separator_spaces, + is_multiline, + ); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); } } @@ -659,6 +744,7 @@ impl<'a> Formattable for Tag<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { let is_multiline = self.is_multiline(); @@ -676,13 +762,14 @@ impl<'a> Formattable for Tag<'a> { buf, Parens::InApply, Newlines::No, + flags, arg_indent, ); } } else { for arg in *args { buf.spaces(1); - arg.format_with_options(buf, Parens::InApply, Newlines::No, indent); + arg.format_with_options(buf, Parens::InApply, Newlines::No, flags, indent); } } } @@ -697,7 +784,14 @@ impl<'a> Formattable for ImplementsClause<'a> { false } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { buf.push_str(self.var.value.extract_spaces().item); buf.spaces(1); buf.push_str(roc_parse::keyword::IMPLEMENTS); @@ -709,7 +803,7 @@ impl<'a> Formattable for ImplementsClause<'a> { buf.push('&'); buf.spaces(1); } - ab.format_with_options(buf, parens, newlines, indent); + ab.format_with_options(buf, parens, newlines, flags, indent); } } } @@ -722,23 +816,30 @@ impl<'a> Formattable for AbilityImpls<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { match self { AbilityImpls::AbilityImpls(impls) => { if newlines == Newlines::Yes { buf.newline(); buf.indent(indent); } - fmt_collection(buf, indent, Braces::Curly, *impls, Newlines::No); + fmt_collection(buf, flags, indent, Braces::Curly, *impls, Newlines::No); } AbilityImpls::SpaceBefore(impls, spaces) => { buf.newline(); buf.indent(indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - impls.format_with_options(buf, parens, Newlines::No, indent); + impls.format_with_options(buf, parens, Newlines::No, flags, indent); } AbilityImpls::SpaceAfter(impls, spaces) => { - impls.format_with_options(buf, parens, newlines, indent); + impls.format_with_options(buf, parens, newlines, flags, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); } } @@ -755,27 +856,34 @@ impl<'a> Formattable for ImplementsAbility<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { match self { ImplementsAbility::ImplementsAbility { ability, impls } => { if newlines == Newlines::Yes { buf.newline(); buf.indent(indent); } - ability.format_with_options(buf, parens, newlines, indent); + ability.format_with_options(buf, parens, newlines, flags, indent); if let Some(impls) = impls { buf.spaces(1); - impls.format_with_options(buf, parens, newlines, indent); + impls.format_with_options(buf, parens, newlines, flags, indent); } } ImplementsAbility::SpaceBefore(ab, spaces) => { buf.newline(); buf.indent(indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - ab.format_with_options(buf, parens, Newlines::No, indent) + ab.format_with_options(buf, parens, Newlines::No, flags, indent) } ImplementsAbility::SpaceAfter(ab, spaces) => { - ab.format_with_options(buf, parens, newlines, indent); + ab.format_with_options(buf, parens, newlines, flags, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); } } @@ -792,7 +900,14 @@ impl<'a> Formattable for ImplementsAbilities<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { match self { ImplementsAbilities::Implements(has_abilities) => { if newlines == Newlines::Yes { @@ -801,16 +916,23 @@ impl<'a> Formattable for ImplementsAbilities<'a> { } buf.push_str(roc_parse::keyword::IMPLEMENTS); buf.spaces(1); - fmt_collection(buf, indent, Braces::Square, *has_abilities, Newlines::No); + fmt_collection( + buf, + flags, + indent, + Braces::Square, + *has_abilities, + Newlines::No, + ); } ImplementsAbilities::SpaceBefore(has_abilities, spaces) => { buf.newline(); buf.indent(indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - has_abilities.format_with_options(buf, parens, Newlines::No, indent) + has_abilities.format_with_options(buf, parens, Newlines::No, flags, indent) } ImplementsAbilities::SpaceAfter(has_abilities, spaces) => { - has_abilities.format_with_options(buf, parens, newlines, indent); + has_abilities.format_with_options(buf, parens, newlines, flags, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); } } @@ -934,7 +1056,14 @@ impl<'a> Formattable for Node<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { match self { Node::DelimitedSequence(braces, lefts, right) => { buf.indent(indent); @@ -945,7 +1074,7 @@ impl<'a> Formattable for Node<'a> { fmt_spaces(buf, sp.iter(), indent); } - l.format_with_options(buf, parens, newlines, indent); + l.format_with_options(buf, parens, newlines, flags, indent); } if !right.is_empty() { @@ -956,7 +1085,7 @@ impl<'a> Formattable for Node<'a> { buf.push(braces.end()); } Node::TypeAnnotation(type_annotation) => { - type_annotation.format_with_options(buf, parens, newlines, indent); + type_annotation.format_with_options(buf, parens, newlines, flags, indent); } } } @@ -1093,10 +1222,12 @@ impl<'a, V: Formattable> Formattable for NodeSpaces<'a, V> { buf: &mut Buf, parens: crate::annotation::Parens, newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { fmt_spaces(buf, self.before.iter(), indent); - self.item.format_with_options(buf, parens, newlines, indent); + self.item + .format_with_options(buf, parens, newlines, flags, indent); fmt_spaces(buf, self.after.iter(), indent); } } diff --git a/crates/compiler/fmt/src/collection.rs b/crates/compiler/fmt/src/collection.rs index 65366f6ec6..60bef6fc2a 100644 --- a/crates/compiler/fmt/src/collection.rs +++ b/crates/compiler/fmt/src/collection.rs @@ -35,6 +35,7 @@ impl Braces { pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable + std::fmt::Debug>( buf: &mut Buf<'buf>, + flags: &crate::annotation::MigrationFlags, indent: u16, braces: Braces, items: Collection<'a, T>, @@ -109,7 +110,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable + std::fmt::D } buf.indent(item_indent); - item.item.format(buf, item_indent); + item.item.format(buf, flags, item_indent); buf.indent(item_indent); buf.push(','); @@ -152,7 +153,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable + std::fmt::D buf.spaces(1); } - item.format(buf, indent); + item.format(buf, flags, indent); if iter.peek().is_some() { buf.push(','); } diff --git a/crates/compiler/fmt/src/def.rs b/crates/compiler/fmt/src/def.rs index 0eb782504e..89b2db0cee 100644 --- a/crates/compiler/fmt/src/def.rs +++ b/crates/compiler/fmt/src/def.rs @@ -1,6 +1,6 @@ use crate::annotation::{ ann_lift_spaces, ann_lift_spaces_after, is_collection_multiline, ty_is_outdentable, - Formattable, Newlines, Parens, + Formattable, MigrationFlags, Newlines, Parens, }; use crate::collection::{fmt_collection, Braces}; use crate::expr::{ @@ -37,6 +37,7 @@ impl<'a> Formattable for Defs<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { let mut prev_spaces = true; @@ -58,8 +59,8 @@ impl<'a> Formattable for Defs<'a> { } match def.item { - Ok(type_def) => type_def.format(buf, indent), - Err(value_def) => value_def.format(buf, indent), + Ok(type_def) => type_def.format(buf, flags, indent), + Err(value_def) => value_def.format(buf, flags, indent), } fmt_spaces(buf, spaces_after.iter(), indent); @@ -413,12 +414,19 @@ impl<'a> Formattable for TypeDef<'a> { } } - fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + _parens: Parens, + newlines: Newlines, + flags: &crate::annotation::MigrationFlags, + indent: u16, + ) { use roc_parse::ast::TypeDef::*; match self { Alias { header, ann } => { - header.format(buf, indent); + header.format(buf, flags, indent); buf.indent(indent); buf.push_str(" :"); @@ -432,7 +440,7 @@ impl<'a> Formattable for TypeDef<'a> { indent + INDENT }; fmt_comments_only(buf, ann.before.iter(), NewlineAt::Bottom, inner_indent); - ann.item.format(buf, inner_indent); + ann.item.format(buf, flags, inner_indent); fmt_spaces(buf, ann.after.iter(), indent); } Opaque { @@ -453,7 +461,7 @@ impl<'a> Formattable for TypeDef<'a> { let make_multiline = ann.is_multiline() || has_abilities_multiline; - fmt_general_def(header, buf, indent, ":=", &ann.value, newlines); + fmt_general_def(header, buf, flags, indent, ":=", &ann.value, newlines); if let Some(has_abilities) = has_abilities { buf.spaces(1); @@ -462,6 +470,7 @@ impl<'a> Formattable for TypeDef<'a> { buf, Parens::NotNeeded, Newlines::from_bool(make_multiline), + flags, indent + INDENT, ); } @@ -471,7 +480,7 @@ impl<'a> Formattable for TypeDef<'a> { loc_implements: _, members, } => { - header.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + header.format_with_options(buf, Parens::NotNeeded, Newlines::No, flags, indent); buf.spaces(1); buf.push_str(roc_parse::keyword::IMPLEMENTS); @@ -482,6 +491,7 @@ impl<'a> Formattable for TypeDef<'a> { buf, Parens::NotNeeded, Newlines::No, + flags, indent + INDENT, ); } else { @@ -490,6 +500,7 @@ impl<'a> Formattable for TypeDef<'a> { buf, Parens::NotNeeded, Newlines::Yes, + flags, indent + INDENT, ); } @@ -509,6 +520,7 @@ impl<'a> Formattable for TypeHeader<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -551,7 +563,7 @@ impl<'a> Formattable for TypeHeader<'a> { buf.push_str("("); } - fmt_pattern(buf, &var.item, vars_indent, Parens::NotNeeded); + fmt_pattern(buf, &var.item, flags, vars_indent, Parens::NotNeeded); buf.indent(vars_indent); @@ -599,6 +611,7 @@ impl<'a> Formattable for ModuleImport<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { let Self { @@ -624,13 +637,20 @@ impl<'a> Formattable for ModuleImport<'a> { fmt_default_spaces(buf, before_name, indent); - name.format(buf, indent); - params.format(buf, indent); - alias.format(buf, indent); + name.format(buf, flags, indent); + params.format(buf, flags, indent); + alias.format(buf, flags, indent); if let Some(exposed) = exposed { - exposed.keyword.format(buf, indent); - fmt_collection(buf, indent, Braces::Square, exposed.item, Newlines::No); + exposed.keyword.format(buf, flags, indent); + fmt_collection( + buf, + flags, + indent, + Braces::Square, + exposed.item, + Newlines::No, + ); } } } @@ -642,11 +662,18 @@ impl<'a> Formattable for ModuleImportParams<'a> { !before.is_empty() || is_collection_multiline(¶ms.value) } - fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + _parens: Parens, + newlines: Newlines, + flags: &crate::annotation::MigrationFlags, + indent: u16, + ) { let ModuleImportParams { before, params } = self; fmt_default_spaces(buf, before, indent); - fmt_collection(buf, indent, Braces::Curly, params.value, newlines); + fmt_collection(buf, flags, indent, Braces::Curly, params.value, newlines); } } @@ -666,6 +693,7 @@ impl<'a> Formattable for IngestedFileImport<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { let Self { @@ -681,12 +709,12 @@ impl<'a> Formattable for IngestedFileImport<'a> { let indent = indent + INDENT; fmt_default_spaces(buf, before_path, indent); - fmt_str_literal(buf, path.value, indent); + fmt_str_literal(buf, path.value, flags, indent); - name.keyword.format(buf, indent); + name.keyword.format(buf, flags, indent); buf.push_str(name.item.value); - annotation.format(buf, indent); + annotation.format(buf, flags, indent); } } @@ -701,6 +729,7 @@ impl<'a> Formattable for ImportedModuleName<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -710,7 +739,7 @@ impl<'a> Formattable for ImportedModuleName<'a> { buf.push_str("."); } - self.name.format(buf, indent); + self.name.format(buf, flags, indent); } } @@ -725,6 +754,7 @@ impl<'a> Formattable for ImportAlias<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + _flags: &crate::annotation::MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -742,6 +772,7 @@ impl Formattable for ImportAsKeyword { buf: &mut Buf<'_>, _parens: crate::annotation::Parens, _newlines: Newlines, + _flags: &crate::annotation::MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -759,6 +790,7 @@ impl Formattable for ImportExposingKeyword { buf: &mut Buf<'_>, _parens: crate::annotation::Parens, _newlines: Newlines, + _flags: &crate::annotation::MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -780,6 +812,7 @@ impl<'a> Formattable for IngestedFileAnnotation<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { let Self { @@ -790,7 +823,7 @@ impl<'a> Formattable for IngestedFileAnnotation<'a> { fmt_default_spaces(buf, before_colon, indent); buf.push_str(":"); buf.spaces(1); - annotation.format(buf, indent); + annotation.format(buf, flags, indent); } } @@ -813,13 +846,21 @@ impl<'a> Formattable for ValueDef<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &crate::annotation::MigrationFlags, + indent: u16, + ) { use roc_parse::ast::ValueDef::*; match self { Annotation(loc_pattern, loc_annotation) => { fmt_general_def( loc_pattern, buf, + flags, indent, ":", &loc_annotation.value, @@ -827,10 +868,21 @@ impl<'a> Formattable for ValueDef<'a> { ); } Body(loc_pattern, loc_expr) => { - fmt_body(buf, true, &loc_pattern.value, &loc_expr.value, indent); + fmt_body( + buf, + true, + &loc_pattern.value, + &loc_expr.value, + flags, + indent, + ); + } + Dbg { condition, .. } => { + fmt_dbg_in_def(buf, condition, self.is_multiline(), flags, indent) + } + Expect { condition, .. } => { + fmt_expect(buf, condition, self.is_multiline(), flags, indent) } - Dbg { condition, .. } => fmt_dbg_in_def(buf, condition, self.is_multiline(), indent), - Expect { condition, .. } => fmt_expect(buf, condition, self.is_multiline(), indent), AnnotatedBody { ann_pattern, ann_type, @@ -838,16 +890,33 @@ impl<'a> Formattable for ValueDef<'a> { body_pattern, body_expr, } => { - fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines); + fmt_general_def( + ann_pattern, + buf, + flags, + indent, + ":", + &ann_type.value, + newlines, + ); fmt_annotated_body_comment(buf, indent, lines_between); buf.newline(); - fmt_body(buf, false, &body_pattern.value, &body_expr.value, indent); + fmt_body( + buf, + false, + &body_pattern.value, + &body_expr.value, + flags, + indent, + ); } - ModuleImport(module_import) => module_import.format(buf, indent), - IngestedFileImport(ingested_file_import) => ingested_file_import.format(buf, indent), - Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, indent), + ModuleImport(module_import) => module_import.format(buf, flags, indent), + IngestedFileImport(ingested_file_import) => { + ingested_file_import.format(buf, flags, indent) + } + Stmt(loc_expr) => loc_expr.format_with_options(buf, parens, newlines, flags, indent), StmtAfterExpr => internal_error!("shouldn't exist before can"), } } @@ -856,12 +925,13 @@ impl<'a> Formattable for ValueDef<'a> { fn fmt_general_def( lhs: L, buf: &mut Buf, + flags: &crate::annotation::MigrationFlags, indent: u16, sep: &str, rhs: &TypeAnnotation, newlines: Newlines, ) { - lhs.format(buf, indent); + lhs.format(buf, flags, indent); buf.indent(indent); if rhs.is_multiline() { @@ -872,9 +942,13 @@ fn fmt_general_def( let rhs_lifted = ann_lift_spaces(buf.text.bump(), rhs); if ty_is_outdentable(&rhs_lifted.item) && rhs_lifted.before.iter().all(|s| s.is_newline()) { - rhs_lifted - .item - .format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + rhs_lifted.item.format_with_options( + buf, + Parens::NotNeeded, + Newlines::No, + flags, + indent, + ); } else { buf.ensure_ends_with_newline(); fmt_comments_only( @@ -883,30 +957,46 @@ fn fmt_general_def( NewlineAt::Bottom, indent + INDENT, ); - rhs_lifted - .item - .format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT); + rhs_lifted.item.format_with_options( + buf, + Parens::NotNeeded, + newlines, + flags, + indent + INDENT, + ); } fmt_comments_only(buf, rhs_lifted.after.iter(), NewlineAt::Bottom, indent); } else { buf.spaces(1); buf.push_str(sep); buf.spaces(1); - rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, flags, indent); } } -fn fmt_dbg_in_def<'a>(buf: &mut Buf, condition: &'a Loc>, _: bool, indent: u16) { +fn fmt_dbg_in_def<'a>( + buf: &mut Buf, + condition: &'a Loc>, + _: bool, + flags: &MigrationFlags, + indent: u16, +) { buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("dbg"); buf.spaces(1); - condition.format(buf, indent); + condition.format(buf, flags, indent); } -fn fmt_expect<'a>(buf: &mut Buf, condition: &'a Loc>, is_multiline: bool, indent: u16) { +fn fmt_expect<'a>( + buf: &mut Buf, + condition: &'a Loc>, + is_multiline: bool, + flags: &crate::annotation::MigrationFlags, + indent: u16, +) { buf.ensure_ends_with_newline(); buf.indent(indent); buf.push_str("expect"); @@ -919,19 +1009,34 @@ fn fmt_expect<'a>(buf: &mut Buf, condition: &'a Loc>, is_multiline: boo indent }; - condition.format(buf, return_indent); + condition.format(buf, flags, return_indent); } -pub fn fmt_value_def(buf: &mut Buf, def: &roc_parse::ast::ValueDef, indent: u16) { - def.format(buf, indent); +pub fn fmt_value_def( + buf: &mut Buf, + def: &roc_parse::ast::ValueDef, + flags: &crate::annotation::MigrationFlags, + indent: u16, +) { + def.format(buf, flags, indent); } -pub fn fmt_type_def(buf: &mut Buf, def: &roc_parse::ast::TypeDef, indent: u16) { - def.format(buf, indent); +pub fn fmt_type_def( + buf: &mut Buf, + def: &roc_parse::ast::TypeDef, + flags: &crate::annotation::MigrationFlags, + indent: u16, +) { + def.format(buf, flags, indent); } -pub fn fmt_defs(buf: &mut Buf, defs: &Defs, indent: u16) { - defs.format(buf, indent); +pub fn fmt_defs( + buf: &mut Buf, + defs: &Defs, + flags: &crate::annotation::MigrationFlags, + indent: u16, +) { + defs.format(buf, flags, indent); } pub fn fmt_annotated_body_comment<'a>( @@ -982,6 +1087,7 @@ pub fn fmt_body<'a>( allow_simplify_empty_record_destructure: bool, pattern: &'a Pattern<'a>, body: &'a Expr<'a>, + flags: &MigrationFlags, indent: u16, ) { let pattern_extracted = pattern.extract_spaces(); @@ -998,10 +1104,10 @@ pub fn fmt_body<'a>( // Don't format the `{} =` for defs with this pattern if is_unit_assignment { - return body.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent); + return body.format_with_options(buf, Parens::NotNeeded, Newlines::No, flags, indent); } - pattern.format_with_options(buf, Parens::InApply, Newlines::No, indent); + pattern.format_with_options(buf, Parens::InApply, Newlines::No, flags, indent); buf.indent(indent); buf.push_str(" ="); @@ -1020,15 +1126,28 @@ pub fn fmt_body<'a>( if is_unit_assignment { fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); - sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + sub_def.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent, + ); } else if should_outdent { buf.spaces(1); - sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + sub_def.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent, + ); } else { body.format_with_options( buf, Parens::NotNeeded, Newlines::Yes, + flags, indent + INDENT, ); } @@ -1041,7 +1160,13 @@ pub fn fmt_body<'a>( .., ) => { buf.spaces(1); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent + INDENT, + ); } Expr::Str(s) => { if is_str_multiline(&s) { @@ -1049,15 +1174,33 @@ pub fn fmt_body<'a>( } else { buf.spaces(1); } - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent + INDENT, + ); } _ if starts_with_block_string_literal(&body) => { buf.ensure_ends_with_newline(); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent + INDENT, + ); } Expr::When(..) => { buf.ensure_ends_with_newline(); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent + INDENT, + ); } Expr::Defs(..) | Expr::BinOps(_, _) | Expr::Backpassing(..) => { // Binop chains always get a newline. Otherwise you can have things like: @@ -1073,7 +1216,13 @@ pub fn fmt_body<'a>( // // This makes it clear what the binop is applying to! buf.newline(); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + body.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent + INDENT, + ); } Expr::ParensAround(&Expr::SpaceBefore(sub_def, _)) => { let needs_indent = !sub_expr_requests_parens(sub_def); @@ -1083,16 +1232,16 @@ pub fn fmt_body<'a>( indent }; buf.spaces(1); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, indent); } _ => { buf.spaces(1); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, indent); } } } else { buf.spaces(1); - body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, indent); } } @@ -1118,6 +1267,7 @@ impl<'a> Formattable for AbilityMember<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &crate::annotation::MigrationFlags, indent: u16, ) { let Spaces { before, item, .. } = self.name.value.extract_spaces(); @@ -1128,6 +1278,6 @@ impl<'a> Formattable for AbilityMember<'a> { buf.spaces(1); buf.push(':'); buf.spaces(1); - self.typ.value.format(buf, indent + INDENT); + self.typ.value.format(buf, flags, indent + INDENT); } } diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs index 6809f26386..c576b408c6 100644 --- a/crates/compiler/fmt/src/expr.rs +++ b/crates/compiler/fmt/src/expr.rs @@ -1,4 +1,6 @@ -use crate::annotation::{except_last, is_collection_multiline, Formattable, Newlines, Parens}; +use crate::annotation::{ + except_last, is_collection_multiline, Formattable, MigrationFlags, Newlines, Parens, +}; use crate::collection::{fmt_collection, Braces}; use crate::def::{fmt_defs, valdef_lift_spaces_before}; use crate::pattern::{fmt_pattern, pattern_lift_spaces, starts_with_inline_comment}; @@ -28,7 +30,14 @@ impl<'a> Formattable for Expr<'a> { expr_is_multiline(self, false) } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { use self::Expr::*; let me = expr_lift_spaces(parens, buf.text.bump(), self); @@ -41,13 +50,13 @@ impl<'a> Formattable for Expr<'a> { SpaceBefore(_sub_expr, _spaces) | SpaceAfter(_sub_expr, _spaces) => unreachable!(), ParensAround(sub_expr) => { if parens == Parens::NotNeeded && !sub_expr_requests_parens(sub_expr) { - sub_expr.format_with_options(buf, Parens::NotNeeded, newlines, indent); + sub_expr.format_with_options(buf, Parens::NotNeeded, newlines, flags, indent); } else { - fmt_parens(sub_expr, buf, indent); + fmt_parens(sub_expr, buf, flags, indent); } } Str(literal) => { - fmt_str_literal(buf, *literal, indent); + fmt_str_literal(buf, *literal, flags, indent); } Var { module_name, ident } => { buf.indent(indent); @@ -75,9 +84,9 @@ impl<'a> Formattable for Expr<'a> { let apply_needs_parens = parens == Parens::InApply; if apply_needs_parens && !loc_args.is_empty() { - fmt_parens(self, buf, indent); + fmt_parens(self, buf, flags, indent); } else { - fmt_apply(loc_expr, loc_args, indent, buf); + fmt_apply(loc_expr, loc_args, flags, indent, buf); } } &Num(string) => { @@ -120,6 +129,7 @@ impl<'a> Formattable for Expr<'a> { buf, None, *fields, + flags, indent, format_assigned_field_multiline, assigned_field_to_space_before, @@ -130,6 +140,7 @@ impl<'a> Formattable for Expr<'a> { buf, Some(RecordPrefix::Update(update)), *fields, + flags, indent, format_assigned_field_multiline, assigned_field_to_space_before, @@ -140,28 +151,29 @@ impl<'a> Formattable for Expr<'a> { buf, Some(RecordPrefix::Mapper(mapper)), *fields, + flags, indent, format_assigned_field_multiline, assigned_field_to_space_before, ); } Closure(loc_patterns, loc_ret) => { - fmt_closure(buf, loc_patterns, loc_ret, indent); + fmt_closure(buf, loc_patterns, loc_ret, flags, indent); } Backpassing(loc_patterns, loc_body, loc_ret) => { - fmt_backpassing(buf, loc_patterns, loc_body, loc_ret, indent); + fmt_backpassing(buf, loc_patterns, loc_body, loc_ret, flags, indent); } Defs(defs, ret) => { let defs_needs_parens = parens == Parens::InOperator || parens == Parens::InApply; if defs_needs_parens { - fmt_parens(self, buf, indent) + fmt_parens(self, buf, flags, indent) } else { // It should theoretically be impossible to *parse* an empty defs list. // (Canonicalization can remove defs later, but that hasn't happened yet!) debug_assert!(!defs.is_empty()); - fmt_defs(buf, defs, indent); + fmt_defs(buf, defs, flags, indent); match &ret.value { SpaceBefore(sub_expr, spaces) => { @@ -174,6 +186,7 @@ impl<'a> Formattable for Expr<'a> { buf, Parens::NotNeeded, Newlines::Yes, + flags, indent, ); } @@ -182,7 +195,13 @@ impl<'a> Formattable for Expr<'a> { buf.indent(indent); // Even if there were no defs, which theoretically should never happen, // still print the return value. - ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + ret.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent, + ); } } } @@ -196,13 +215,21 @@ impl<'a> Formattable for Expr<'a> { extra_args, continuation, } => { - fmt_dbg_stmt(buf, first, extra_args, continuation, parens, indent); + fmt_dbg_stmt(buf, first, extra_args, continuation, parens, flags, indent); } LowLevelDbg(_, _, _) => unreachable!( "LowLevelDbg should only exist after desugaring, not during formatting" ), Return(return_value, after_return) => { - fmt_return(buf, return_value, after_return, parens, newlines, indent); + fmt_return( + buf, + return_value, + after_return, + parens, + newlines, + flags, + indent, + ); } If { if_thens: branches, @@ -215,13 +242,18 @@ impl<'a> Formattable for Expr<'a> { final_else, self.is_multiline(), *indented_else, + flags, indent, ); } - When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), - Tuple(items) => fmt_expr_collection(buf, indent, Braces::Round, *items, Newlines::No), - List(items) => fmt_expr_collection(buf, indent, Braces::Square, *items, Newlines::No), - BinOps(lefts, right) => fmt_binops(buf, lefts, right, indent), + When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, flags, indent), + Tuple(items) => { + fmt_expr_collection(buf, flags, indent, Braces::Round, *items, Newlines::No) + } + List(items) => { + fmt_expr_collection(buf, flags, indent, Braces::Square, *items, Newlines::No) + } + BinOps(lefts, right) => fmt_binops(buf, lefts, right, flags, indent), UnaryOp(sub_expr, unary_op) => { buf.indent(indent); match &unary_op.value { @@ -255,7 +287,7 @@ impl<'a> Formattable for Expr<'a> { if needs_parens { // Unary negation can't be followed by whitespace (which is what a newline is) - so // we need to wrap the negated value in parens. - fmt_parens(&sub_expr.value, buf, indent); + fmt_parens(&sub_expr.value, buf, flags, indent); } else { if matches!(unary_op.value, called_via::UnaryOp::Not) && requires_space_after_unary(&lifted.item) @@ -281,9 +313,13 @@ impl<'a> Formattable for Expr<'a> { if !before_all_newlines { format_spaces(buf, lifted.before, newlines, inner_indent); } - lifted - .item - .format_with_options(buf, inner_parens, newlines, inner_indent); + lifted.item.format_with_options( + buf, + inner_parens, + newlines, + flags, + inner_indent, + ); format_spaces(buf, lifted.after, newlines, inner_indent); } } @@ -301,17 +337,17 @@ impl<'a> Formattable for Expr<'a> { buf.push_str(key); } RecordAccess(expr, key) => { - expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); + expr.format_with_options(buf, Parens::InApply, Newlines::Yes, flags, indent); buf.push('.'); buf.push_str(key); } TupleAccess(expr, key) => { - expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); + expr.format_with_options(buf, Parens::InApply, Newlines::Yes, flags, indent); buf.push('.'); buf.push_str(key); } TrySuffix { expr, target } => { - expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); + expr.format_with_options(buf, Parens::InApply, Newlines::Yes, flags, indent); match target { TryTarget::Task => buf.push('!'), TryTarget::Result => buf.push('?'), @@ -323,7 +359,7 @@ impl<'a> Formattable for Expr<'a> { } MalformedSuffixed(loc_expr) => { buf.indent(indent); - loc_expr.format_with_options(buf, parens, newlines, indent); + loc_expr.format_with_options(buf, parens, newlines, flags, indent); } PrecedenceConflict { .. } => {} EmptyRecordBuilder { .. } => {} @@ -465,6 +501,7 @@ fn lower<'a, 'b: 'a>(arena: &'b Bump, lifted: Spaces<'b, Expr<'b>>) -> Expr<'b> fn fmt_expr_collection( buf: &mut Buf<'_>, + flags: &MigrationFlags, indent: u16, braces: Braces, items: Collection<'_, &Loc>>, @@ -488,7 +525,7 @@ fn fmt_expr_collection( let new_items = Collection::with_items_and_comments(arena, new_items.into_bump_slice(), final_comments); - fmt_collection(buf, indent, braces, new_items, newlines) + fmt_collection(buf, flags, indent, braces, new_items, newlines) } fn requires_space_after_unary(item: &Expr<'_>) -> bool { @@ -513,6 +550,7 @@ fn requires_space_after_unary(item: &Expr<'_>) -> bool { fn fmt_apply( loc_expr: &Loc>, loc_args: &[&Loc>], + flags: &MigrationFlags, indent: u16, buf: &mut Buf<'_>, ) { @@ -568,9 +606,9 @@ fn fmt_apply( || expr_needs_parens_in_apply(&loc_expr.value); if expr_needs_parens { - fmt_parens(&loc_expr.value, buf, indent); + fmt_parens(&loc_expr.value, buf, flags, indent); } else { - loc_expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent); + loc_expr.format_with_options(buf, Parens::InApply, Newlines::Yes, flags, indent); } for loc_arg in loc_args.iter() { @@ -583,17 +621,17 @@ fn fmt_apply( let arg = loc_arg.extract_spaces(); arg.item - .format_with_options(buf, Parens::InApply, Newlines::Yes, arg_indent); + .format_with_options(buf, Parens::InApply, Newlines::Yes, flags, arg_indent); } else if needs_indent { let arg = loc_arg.extract_spaces(); fmt_spaces(buf, arg.before.iter(), arg_indent); buf.ensure_ends_with_newline(); arg.item - .format_with_options(buf, Parens::InApply, Newlines::Yes, arg_indent); + .format_with_options(buf, Parens::InApply, Newlines::Yes, flags, arg_indent); fmt_spaces(buf, arg.after.iter(), arg_indent); } else { buf.spaces(1); - loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, arg_indent); + loc_arg.format_with_options(buf, Parens::InApply, Newlines::Yes, flags, arg_indent); } } } @@ -625,7 +663,7 @@ fn expr_ends_in_closure(expr: &Expr<'_>) -> bool { } } -fn fmt_parens(sub_expr: &Expr<'_>, buf: &mut Buf<'_>, indent: u16) { +fn fmt_parens(sub_expr: &Expr<'_>, buf: &mut Buf<'_>, flags: &MigrationFlags, indent: u16) { let should_add_newlines = match sub_expr { Expr::Closure(..) | Expr::SpaceBefore(..) | Expr::SpaceAfter(Expr::Closure(..), ..) => { false @@ -648,7 +686,7 @@ fn fmt_parens(sub_expr: &Expr<'_>, buf: &mut Buf<'_>, indent: u16) { indent }; - sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, next_indent); + sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, next_indent); if !matches!(sub_expr, Expr::SpaceAfter(..)) && should_add_newlines { buf.newline(); @@ -741,7 +779,7 @@ fn fmt_str_body(body: &str, buf: &mut Buf) { } } -fn format_str_segment(seg: &StrSegment, buf: &mut Buf, indent: u16) { +fn format_str_segment(seg: &StrSegment, buf: &mut Buf, flags: &MigrationFlags, indent: u16) { use StrSegment::*; match seg { @@ -772,6 +810,7 @@ fn format_str_segment(seg: &StrSegment, buf: &mut Buf, indent: u16) { buf, Parens::NotNeeded, // We already printed parens! Newlines::No, // Interpolations can never have newlines + flags, indent, ); buf.push(')'); @@ -800,7 +839,7 @@ fn push_op(buf: &mut Buf, op: BinOp) { } } -pub fn fmt_str_literal(buf: &mut Buf, literal: StrLiteral, indent: u16) { +pub fn fmt_str_literal(buf: &mut Buf, literal: StrLiteral, flags: &MigrationFlags, indent: u16) { use roc_parse::ast::StrLiteral::*; match literal { @@ -829,7 +868,7 @@ pub fn fmt_str_literal(buf: &mut Buf, literal: StrLiteral, indent: u16) { buf.indent(indent); buf.push('"'); for seg in segments.iter() { - format_str_segment(seg, buf, 0) + format_str_segment(seg, buf, flags, 0) } buf.push('"'); } @@ -845,7 +884,7 @@ pub fn fmt_str_literal(buf: &mut Buf, literal: StrLiteral, indent: u16) { // only add indent if the line isn't empty if *seg != StrSegment::Plaintext("\n") { buf.indent(indent); - format_str_segment(seg, buf, indent); + format_str_segment(seg, buf, flags, indent); } else { buf.push_newline_literal(); } @@ -1044,6 +1083,7 @@ fn fmt_binops<'a>( buf: &mut Buf, lefts: &'a [(Loc>, Loc)], loc_right_side: &'a Loc>, + flags: &MigrationFlags, indent: u16, ) { let is_multiline = loc_right_side.value.is_multiline() @@ -1060,12 +1100,13 @@ fn fmt_binops<'a>( || starts_with_unary_minus(lifted_left_side.item); if need_parens { - fmt_parens(&lifted_left_side.item, buf, indent); + fmt_parens(&lifted_left_side.item, buf, flags, indent); } else { lifted_left_side.item.format_with_options( buf, Parens::InOperator, Newlines::Yes, + flags, indent, ); } @@ -1092,11 +1133,15 @@ fn fmt_binops<'a>( || starts_with_unary_minus(lifted_right_side.item); if need_parens { - fmt_parens(&lifted_right_side.item, buf, indent); + fmt_parens(&lifted_right_side.item, buf, flags, indent); } else { - lifted_right_side - .item - .format_with_options(buf, Parens::InOperator, Newlines::Yes, indent); + lifted_right_side.item.format_with_options( + buf, + Parens::InOperator, + Newlines::Yes, + flags, + indent, + ); } format_spaces(buf, lifted_right_side.after, Newlines::Yes, indent); @@ -1153,6 +1198,7 @@ fn fmt_when<'a>( buf: &mut Buf, loc_condition: &'a Loc>, branches: &[&'a WhenBranch<'a>], + flags: &MigrationFlags, indent: u16, ) { let is_multiline_condition = loc_condition.is_multiline(); @@ -1184,7 +1230,7 @@ fn fmt_when<'a>( NewlineAt::None }; - expr_above.format(buf, condition_indent); + expr_above.format(buf, flags, condition_indent); fmt_comments_only( buf, spaces_below_expr.iter(), @@ -1194,20 +1240,20 @@ fn fmt_when<'a>( buf.newline(); } _ => { - expr_below.format(buf, condition_indent); + expr_below.format(buf, flags, condition_indent); } } } _ => { buf.newline(); - loc_condition.format(buf, condition_indent); + loc_condition.format(buf, flags, condition_indent); buf.newline(); } } buf.indent(indent); } else { buf.spaces(1); - loc_condition.format(buf, indent); + loc_condition.format(buf, flags, indent); buf.spaces(1); } buf.push_str("is"); @@ -1256,7 +1302,7 @@ fn fmt_when<'a>( } } - fmt_pattern(buf, sub_pattern, indent + INDENT, Parens::NotNeeded); + fmt_pattern(buf, sub_pattern, flags, indent + INDENT, Parens::NotNeeded); } other => { if branch_index > 0 { @@ -1268,7 +1314,7 @@ fn fmt_when<'a>( } } - fmt_pattern(buf, other, indent + INDENT, Parens::NotNeeded); + fmt_pattern(buf, other, flags, indent + INDENT, Parens::NotNeeded); } } } else { @@ -1282,14 +1328,26 @@ fn fmt_when<'a>( buf.spaces(1); - fmt_pattern(buf, &pattern.value, indent + INDENT, Parens::NotNeeded); + fmt_pattern( + buf, + &pattern.value, + flags, + indent + INDENT, + Parens::NotNeeded, + ); } } if let Some(guard_expr) = &branch.guard { buf.push_str(" if"); buf.spaces(1); - guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); + guard_expr.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent + INDENT, + ); } buf.push_str(" ->"); @@ -1308,6 +1366,7 @@ fn fmt_when<'a>( buf, Parens::NotNeeded, Newlines::Yes, + flags, indent + 2 * INDENT, ); } @@ -1322,6 +1381,7 @@ fn fmt_when<'a>( buf, Parens::NotNeeded, Newlines::Yes, + flags, indent + 2 * INDENT, ); } @@ -1337,6 +1397,7 @@ fn fmt_dbg_stmt<'a>( extra_args: &'a [&'a Loc>], continuation: &'a Loc>, parens: Parens, + flags: &MigrationFlags, indent: u16, ) { let mut args = Vec::with_capacity_in(extra_args.len() + 1, buf.text.bump()); @@ -1348,12 +1409,12 @@ fn fmt_dbg_stmt<'a>( args.into_bump_slice(), called_via::CalledVia::Space, ) - .format_with_options(buf, parens, Newlines::Yes, indent); + .format_with_options(buf, parens, Newlines::Yes, flags, indent); // Always put a newline after the `dbg` line(s) buf.ensure_ends_with_newline(); - continuation.format(buf, indent); + continuation.format(buf, flags, indent); } fn fmt_return<'a>( @@ -1362,6 +1423,7 @@ fn fmt_return<'a>( after_return: &Option<&'a Loc>>, parens: Parens, newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { buf.ensure_ends_with_newline(); @@ -1380,7 +1442,7 @@ fn fmt_return<'a>( indent }; - return_value.format_with_options(buf, parens, Newlines::No, return_indent); + return_value.format_with_options(buf, parens, Newlines::No, flags, return_indent); if let Some(after_return) = after_return { let lifted = expr_lift_spaces(Parens::NotNeeded, buf.text.bump(), &after_return.value); @@ -1391,7 +1453,7 @@ fn fmt_return<'a>( } lifted .item - .format_with_options(buf, parens, newlines, indent); + .format_with_options(buf, parens, newlines, flags, indent); fmt_spaces(buf, lifted.after.iter(), indent); } else if parens != Parens::NotNeeded { buf.ensure_ends_with_newline(); @@ -1404,6 +1466,7 @@ fn fmt_if<'a>( final_else: &'a Loc>, is_multiline: bool, indented_else: bool, + flags: &MigrationFlags, indent: u16, ) { // let is_multiline_then = loc_then.is_multiline(); @@ -1442,7 +1505,7 @@ fn fmt_if<'a>( match &expr_below { Expr::SpaceAfter(expr_above, spaces_after_expr) => { - expr_above.format(buf, return_indent); + expr_above.format(buf, flags, return_indent); // If any of the spaces is a newline, add a newline at the top. // Otherwise leave it as just a comment. @@ -1465,28 +1528,28 @@ fn fmt_if<'a>( } _ => { - expr_below.format(buf, return_indent); + expr_below.format(buf, flags, return_indent); } } } Expr::SpaceAfter(expr_above, spaces_below_expr) => { buf.newline(); - expr_above.format(buf, return_indent); + expr_above.format(buf, flags, return_indent); fmt_comments_only(buf, spaces_below_expr.iter(), NewlineAt::Top, return_indent); buf.newline(); } _ => { buf.newline(); - loc_condition.format(buf, return_indent); + loc_condition.format(buf, flags, return_indent); buf.newline(); } } buf.indent(indent); } else { buf.spaces(1); - loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + loc_condition.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, indent); buf.spaces(1); } @@ -1501,7 +1564,7 @@ fn fmt_if<'a>( match &expr_below { Expr::SpaceAfter(expr_above, spaces_above) => { - expr_above.format(buf, return_indent); + expr_above.format(buf, flags, return_indent); // If any of the spaces is a newline, add a newline at the top. // Otherwise leave it as just a comment. @@ -1519,20 +1582,20 @@ fn fmt_if<'a>( } _ => { - expr_below.format(buf, return_indent); + expr_below.format(buf, flags, return_indent); } } } _ => { buf.newline(); - loc_then.format(buf, return_indent); + loc_then.format(buf, flags, return_indent); buf.newline(); } } } else { buf.push_str(""); buf.spaces(1); - loc_then.format(buf, return_indent); + loc_then.format(buf, flags, return_indent); } } @@ -1551,13 +1614,14 @@ fn fmt_if<'a>( buf.spaces(1); } let indent = if indented_else { indent } else { return_indent }; - final_else.format(buf, indent); + final_else.format(buf, flags, indent); } fn fmt_closure<'a>( buf: &mut Buf, loc_patterns: &'a [Loc>], loc_ret: &'a Loc>, + flags: &MigrationFlags, indent: u16, ) { use self::Expr::*; @@ -1598,7 +1662,7 @@ fn fmt_closure<'a>( } arg.item - .format_with_options(buf, Parens::InAsPattern, Newlines::No, indent); + .format_with_options(buf, Parens::InAsPattern, Newlines::No, flags, indent); if !arg.after.is_empty() { if starts_with_inline_comment(arg.after.iter()) { @@ -1653,20 +1717,38 @@ fn fmt_closure<'a>( if should_outdent { buf.spaces(1); - sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + sub_expr.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + indent, + ); } else { - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + loc_ret.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + body_indent, + ); } } Record { .. } | List { .. } => { - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent); + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, indent); } _ => { - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + loc_ret.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + flags, + body_indent, + ); } } } else { - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, body_indent); } } @@ -1675,6 +1757,7 @@ fn fmt_backpassing<'a>( loc_patterns: &'a [Loc>], loc_body: &'a Loc>, loc_ret: &'a Loc>, + flags: &MigrationFlags, outer_indent: u16, ) { use self::Expr::*; @@ -1720,6 +1803,7 @@ fn fmt_backpassing<'a>( buf, needs_parens, Newlines::No, + flags, if first { outer_indent } else { arg_indent }, ); fmt_comments_only(buf, pat.after.iter(), NewlineAt::Bottom, arg_indent); @@ -1759,8 +1843,8 @@ fn fmt_backpassing<'a>( } }; - loc_body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, body_indent); - loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, outer_indent); + loc_body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, body_indent); + loc_ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, flags, outer_indent); } fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool { @@ -1782,12 +1866,13 @@ fn fmt_record_like<'a, Field, Format, ToSpaceBefore>( buf: &mut Buf, prefix: Option>, fields: Collection<'a, Loc>, + flags: &MigrationFlags, indent: u16, format_field_multiline: Format, to_space_before: ToSpaceBefore, ) where Field: Formattable, - Format: Fn(&mut Buf, &Field, u16, &str), + Format: Fn(&mut Buf, &Field, &MigrationFlags, u16, &str), ToSpaceBefore: Fn(&'a Field) -> Option<(&'a Field, &'a [CommentOrNewline<'a>])>, { let loc_fields = fields.items; @@ -1806,13 +1891,13 @@ fn fmt_record_like<'a, Field, Format, ToSpaceBefore>( // doesnt make sense. Some(RecordPrefix::Update(record_var)) => { buf.spaces(1); - record_var.format(buf, indent); + record_var.format(buf, flags, indent); buf.indent(indent); buf.push_str(" &"); } Some(RecordPrefix::Mapper(mapper_var)) => { buf.spaces(1); - mapper_var.format(buf, indent); + mapper_var.format(buf, flags, indent); buf.indent(indent); buf.push_str(" <-"); } @@ -1848,7 +1933,7 @@ fn fmt_record_like<'a, Field, Format, ToSpaceBefore>( } } - format_field_multiline(buf, &field.value, field_indent, ""); + format_field_multiline(buf, &field.value, flags, field_indent, ""); } if count_leading_newlines(final_comments.iter()) > 1 { @@ -1864,7 +1949,13 @@ fn fmt_record_like<'a, Field, Format, ToSpaceBefore>( let field_indent = indent; let mut iter = loc_fields.iter().peekable(); while let Some(field) = iter.next() { - field.format_with_options(buf, Parens::NotNeeded, Newlines::No, field_indent); + field.format_with_options( + buf, + Parens::NotNeeded, + Newlines::No, + flags, + field_indent, + ); if iter.peek().is_some() { buf.push_str(","); @@ -1886,6 +1977,7 @@ fn fmt_record_like<'a, Field, Format, ToSpaceBefore>( fn format_assigned_field_multiline( buf: &mut Buf, field: &AssignedField, + flags: &MigrationFlags, indent: u16, separator_prefix: &str, ) where @@ -1906,7 +1998,7 @@ fn format_assigned_field_multiline( buf.push_str(separator_prefix); buf.push_str(":"); buf.spaces(1); - ann.value.format(buf, indent); + ann.value.format(buf, flags, indent); buf.push(','); } OptionalValue(name, spaces, ann) => { @@ -1922,7 +2014,7 @@ fn format_assigned_field_multiline( buf.push_str(separator_prefix); buf.push_str("?"); buf.spaces(1); - ann.value.format(buf, indent); + ann.value.format(buf, flags, indent); buf.push(','); } IgnoredValue(name, spaces, ann) => { @@ -1939,7 +2031,7 @@ fn format_assigned_field_multiline( buf.push_str(separator_prefix); buf.push_str(":"); buf.spaces(1); - ann.value.format(buf, indent); + ann.value.format(buf, flags, indent); buf.push(','); } LabelOnly(name) => { @@ -1956,7 +2048,7 @@ fn format_assigned_field_multiline( // ``` // we'd like to preserve this - format_assigned_field_multiline(buf, sub_field, indent, separator_prefix); + format_assigned_field_multiline(buf, sub_field, flags, indent, separator_prefix); } AssignedField::SpaceAfter(sub_field, spaces) => { // We have something like that: @@ -1970,7 +2062,7 @@ fn format_assigned_field_multiline( // # comment // otherfield // ``` - format_assigned_field_multiline(buf, sub_field, indent, separator_prefix); + format_assigned_field_multiline(buf, sub_field, flags, indent, separator_prefix); fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent); } } diff --git a/crates/compiler/fmt/src/header.rs b/crates/compiler/fmt/src/header.rs index 9b10a8d7c9..d6d9566459 100644 --- a/crates/compiler/fmt/src/header.rs +++ b/crates/compiler/fmt/src/header.rs @@ -1,6 +1,6 @@ use std::cmp::max; -use crate::annotation::{is_collection_multiline, Formattable, Newlines, Parens}; +use crate::annotation::{is_collection_multiline, Formattable, MigrationFlags, Newlines, Parens}; use crate::collection::{fmt_collection, Braces}; use crate::expr::fmt_str_literal; use crate::spaces::{fmt_comments_only, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT}; @@ -15,23 +15,27 @@ use roc_parse::header::{ use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; -pub fn fmt_header<'a>(buf: &mut Buf<'_>, header: &'a SpacesBefore<'a, Header<'a>>) { +pub fn fmt_header<'a>( + buf: &mut Buf<'_>, + header: &'a SpacesBefore<'a, Header<'a>>, + flags: &MigrationFlags, +) { fmt_comments_only(buf, header.before.iter(), NewlineAt::Bottom, 0); match &header.item { Header::Module(header) => { - fmt_module_header(buf, header); + fmt_module_header(buf, header, flags); } Header::App(header) => { - fmt_app_header(buf, header); + fmt_app_header(buf, header, flags); } Header::Package(header) => { - fmt_package_header(buf, header); + fmt_package_header(buf, header, flags); } Header::Platform(header) => { - fmt_platform_header(buf, header); + fmt_platform_header(buf, header, flags); } Header::Hosted(header) => { - fmt_hosted_header(buf, header); + fmt_hosted_header(buf, header, flags); } } } @@ -49,6 +53,7 @@ macro_rules! keywords { buf: &mut Buf<'_>, _parens: crate::annotation::Parens, _newlines: Newlines, + _flags: &crate::annotation::MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -84,10 +89,11 @@ impl Formattable for Option { buf: &mut Buf, parens: crate::annotation::Parens, newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { if let Some(v) = self { - v.format_with_options(buf, parens, newlines, indent); + v.format_with_options(buf, parens, newlines, flags, indent); } } } @@ -109,12 +115,13 @@ impl<'a> Formattable for ProvidesTo<'a> { buf: &mut Buf, _parens: crate::annotation::Parens, _newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { - self.provides_keyword.format(buf, indent); - fmt_provides(buf, self.entries, self.types, indent); - self.to_keyword.format(buf, indent); - fmt_to(buf, self.to.value, indent); + self.provides_keyword.format(buf, flags, indent); + fmt_provides(buf, self.entries, self.types, flags, indent); + self.to_keyword.format(buf, flags, indent); + fmt_to(buf, self.to.value, flags, indent); } } @@ -128,9 +135,10 @@ impl<'a> Formattable for PlatformRequires<'a> { buf: &mut Buf, _parens: crate::annotation::Parens, _newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { - fmt_requires(buf, self, indent); + fmt_requires(buf, self, flags, indent); } } @@ -144,10 +152,12 @@ impl<'a, V: Formattable> Formattable for Spaces<'a, V> { buf: &mut Buf, parens: crate::annotation::Parens, newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { fmt_default_spaces(buf, self.before, indent); - self.item.format_with_options(buf, parens, newlines, indent); + self.item + .format_with_options(buf, parens, newlines, flags, indent); fmt_default_spaces(buf, self.after, indent); } } @@ -157,14 +167,22 @@ impl<'a, K: Formattable, V: Formattable> Formattable for KeywordItem<'a, K, V> { self.keyword.is_multiline() || self.item.is_multiline() } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, newlines: Newlines, indent: u16) { + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { self.keyword - .format_with_options(buf, parens, newlines, indent); - self.item.format_with_options(buf, parens, newlines, indent); + .format_with_options(buf, parens, newlines, flags, indent); + self.item + .format_with_options(buf, parens, newlines, flags, indent); } } -pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) { +pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>, flags: &MigrationFlags) { buf.indent(0); buf.push_str("module"); @@ -177,6 +195,7 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) { fmt_collection( buf, + flags, indent, Braces::Curly, params.pattern.value, @@ -188,10 +207,10 @@ pub fn fmt_module_header<'a>(buf: &mut Buf, header: &'a ModuleHeader<'a>) { indent = fmt_spaces_with_outdent(buf, params.after_arrow, indent); } - fmt_exposes(buf, header.exposes, indent); + fmt_exposes(buf, header.exposes, flags, indent); } -pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) { +pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>, flags: &MigrationFlags) { buf.indent(0); buf.push_str("hosted"); let indent = INDENT; @@ -199,21 +218,21 @@ pub fn fmt_hosted_header<'a>(buf: &mut Buf, header: &'a HostedHeader<'a>) { buf.push_str(header.name.value.as_str()); - header.exposes.keyword.format(buf, indent); - fmt_exposes(buf, header.exposes.item, indent); - header.imports.keyword.format(buf, indent); - fmt_imports(buf, header.imports.item, indent); + header.exposes.keyword.format(buf, flags, indent); + fmt_exposes(buf, header.exposes.item, flags, indent); + header.imports.keyword.format(buf, flags, indent); + fmt_imports(buf, header.imports.item, flags, indent); } -pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>) { +pub fn fmt_app_header<'a>(buf: &mut Buf, header: &'a AppHeader<'a>, flags: &MigrationFlags) { buf.indent(0); buf.push_str("app"); let indent = fmt_spaces_with_outdent(buf, header.before_provides, 0); - fmt_exposes(buf, header.provides, indent); + fmt_exposes(buf, header.provides, flags, indent); let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent); - fmt_packages(buf, header.packages.value, indent); + fmt_packages(buf, header.packages.value, flags, indent); } pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], indent: u16) -> u16 { @@ -227,18 +246,26 @@ pub fn fmt_spaces_with_outdent(buf: &mut Buf, spaces: &[CommentOrNewline], inden } } -pub fn fmt_package_header<'a>(buf: &mut Buf, header: &'a PackageHeader<'a>) { +pub fn fmt_package_header<'a>( + buf: &mut Buf, + header: &'a PackageHeader<'a>, + flags: &MigrationFlags, +) { buf.indent(0); buf.push_str("package"); let indent = fmt_spaces_with_outdent(buf, header.before_exposes, 0); - fmt_exposes(buf, header.exposes, indent); + fmt_exposes(buf, header.exposes, flags, indent); let indent = fmt_spaces_with_outdent(buf, header.before_packages, indent); - fmt_packages(buf, header.packages.value, indent); + fmt_packages(buf, header.packages.value, flags, indent); } -pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) { +pub fn fmt_platform_header<'a>( + buf: &mut Buf, + header: &'a PlatformHeader<'a>, + flags: &MigrationFlags, +) { buf.indent(0); buf.push_str("platform"); let indent = INDENT; @@ -246,23 +273,31 @@ pub fn fmt_platform_header<'a>(buf: &mut Buf, header: &'a PlatformHeader<'a>) { fmt_package_name(buf, header.name.value, indent); - header.requires.format(buf, indent); - header.exposes.keyword.format(buf, indent); - fmt_exposes(buf, header.exposes.item, indent); - header.packages.keyword.format(buf, indent); - fmt_packages(buf, header.packages.item, indent); - header.imports.keyword.format(buf, indent); - fmt_imports(buf, header.imports.item, indent); - header.provides.keyword.format(buf, indent); - fmt_provides(buf, header.provides.item, None, indent); + header.requires.format(buf, flags, indent); + header.exposes.keyword.format(buf, flags, indent); + fmt_exposes(buf, header.exposes.item, flags, indent); + header.packages.keyword.format(buf, flags, indent); + fmt_packages(buf, header.packages.item, flags, indent); + header.imports.keyword.format(buf, flags, indent); + fmt_imports(buf, header.imports.item, flags, indent); + header.provides.keyword.format(buf, flags, indent); + fmt_provides(buf, header.provides.item, None, flags, indent); } -fn fmt_requires(buf: &mut Buf, requires: &PlatformRequires, indent: u16) { - fmt_collection(buf, indent, Braces::Curly, requires.rigids, Newlines::No); +fn fmt_requires(buf: &mut Buf, requires: &PlatformRequires, flags: &MigrationFlags, indent: u16) { + fmt_collection( + buf, + flags, + indent, + Braces::Curly, + requires.rigids, + Newlines::No, + ); buf.spaces(1); fmt_collection( buf, + flags, indent, Braces::Curly, requires.signatures, @@ -280,6 +315,7 @@ impl<'a> Formattable for TypedIdent<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -288,7 +324,7 @@ impl<'a> Formattable for TypedIdent<'a> { buf.push(':'); buf.spaces(1); - self.ann.value.format(buf, indent); + self.ann.value.format(buf, flags, indent); } } @@ -316,18 +352,19 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> { buf: &mut Buf, parens: crate::annotation::Parens, newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { match self { Spaced::Item(item) => { - item.format_with_options(buf, parens, newlines, indent); + item.format_with_options(buf, parens, newlines, flags, indent); } Spaced::SpaceBefore(item, spaces) => { fmt_spaces(buf, spaces.iter(), indent); - item.format_with_options(buf, parens, newlines, indent); + item.format_with_options(buf, parens, newlines, flags, indent); } Spaced::SpaceAfter(item, spaces) => { - item.format_with_options(buf, parens, newlines, indent); + item.format_with_options(buf, parens, newlines, flags, indent); fmt_spaces(buf, spaces.iter(), indent); } } @@ -337,25 +374,48 @@ impl<'a, T: Formattable> Formattable for Spaced<'a, T> { fn fmt_imports<'a>( buf: &mut Buf, loc_entries: Collection<'a, Loc>>>, + flags: &MigrationFlags, indent: u16, ) { - fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No) + fmt_collection( + buf, + flags, + indent, + Braces::Square, + loc_entries, + Newlines::No, + ) } fn fmt_provides<'a>( buf: &mut Buf, loc_exposed_names: Collection<'a, Loc>>>, loc_provided_types: Option>>>>, + flags: &MigrationFlags, indent: u16, ) { - fmt_collection(buf, indent, Braces::Square, loc_exposed_names, Newlines::No); + fmt_collection( + buf, + flags, + 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, Braces::Curly, loc_provided, Newlines::No); + fmt_collection( + buf, + flags, + indent, + Braces::Curly, + loc_provided, + Newlines::No, + ); } } -fn fmt_to(buf: &mut Buf, to: To, indent: u16) { +fn fmt_to(buf: &mut Buf, to: To, _flags: &MigrationFlags, indent: u16) { match to { To::ExistingPackage(name) => { buf.push_str(name); @@ -367,9 +427,17 @@ fn fmt_to(buf: &mut Buf, to: To, indent: u16) { fn fmt_exposes( buf: &mut Buf, loc_entries: Collection<'_, Loc>>, + flags: &MigrationFlags, indent: u16, ) { - fmt_collection(buf, indent, Braces::Square, loc_entries, Newlines::No) + fmt_collection( + buf, + flags, + indent, + Braces::Square, + loc_entries, + Newlines::No, + ) } pub trait FormatName { @@ -398,6 +466,7 @@ impl<'a> Formattable for ModuleName<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + _flags: &MigrationFlags, _indent: u16, ) { buf.push_str(self.as_str()); @@ -414,6 +483,7 @@ impl<'a> Formattable for ExposedName<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + _flags: &MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -430,9 +500,10 @@ impl<'a> FormatName for ExposedName<'a> { fn fmt_packages<'a>( buf: &mut Buf, loc_entries: Collection<'a, Loc>>>, + flags: &MigrationFlags, indent: u16, ) { - fmt_collection(buf, indent, Braces::Curly, loc_entries, Newlines::No) + fmt_collection(buf, flags, indent, Braces::Curly, loc_entries, Newlines::No) } impl<'a> Formattable for PackageEntry<'a> { @@ -445,9 +516,10 @@ impl<'a> Formattable for PackageEntry<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { - fmt_packages_entry(buf, self, indent); + fmt_packages_entry(buf, self, flags, indent); } } @@ -461,12 +533,13 @@ impl<'a> Formattable for ImportsEntry<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + flags: &MigrationFlags, indent: u16, ) { - fmt_imports_entry(buf, self, indent); + fmt_imports_entry(buf, self, flags, indent); } } -fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, indent: u16) { +fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, _flags: &MigrationFlags, indent: u16) { buf.push_str(entry.shorthand); buf.push(':'); fmt_default_spaces(buf, entry.spaces_after_shorthand, indent); @@ -482,7 +555,7 @@ fn fmt_packages_entry(buf: &mut Buf, entry: &PackageEntry, indent: u16) { fmt_package_name(buf, entry.package_name.value, indent); } -fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, indent: u16) { +fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, flags: &MigrationFlags, indent: u16) { use roc_parse::header::ImportsEntry::*; buf.indent(indent); @@ -496,6 +569,7 @@ fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, indent: u16) { fmt_collection( buf, + flags, indent, Braces::Curly, *loc_exposes_entries, @@ -512,14 +586,14 @@ fn fmt_imports_entry(buf: &mut Buf, entry: &ImportsEntry, indent: u16) { if !entries.is_empty() { buf.push('.'); - fmt_collection(buf, indent, Braces::Curly, *entries, Newlines::No) + fmt_collection(buf, flags, indent, Braces::Curly, *entries, Newlines::No) } } IngestedFile(file_name, typed_ident) => { - fmt_str_literal(buf, *file_name, indent); + fmt_str_literal(buf, *file_name, flags, indent); buf.push_str_allow_spaces(" as "); - typed_ident.format(buf, 0); + typed_ident.format(buf, flags, 0); } } } diff --git a/crates/compiler/fmt/src/pattern.rs b/crates/compiler/fmt/src/pattern.rs index ad9aff4f4e..b318bfbb60 100644 --- a/crates/compiler/fmt/src/pattern.rs +++ b/crates/compiler/fmt/src/pattern.rs @@ -1,4 +1,4 @@ -use crate::annotation::{Formattable, Newlines, Parens}; +use crate::annotation::{Formattable, MigrationFlags, Newlines, Parens}; use crate::expr::{ expr_is_multiline, expr_lift_spaces_after, fmt_str_literal, format_sq_literal, is_str_multiline, }; @@ -11,8 +11,14 @@ use roc_parse::ast::{ use roc_parse::expr::merge_spaces; use roc_region::all::Loc; -pub fn fmt_pattern<'a>(buf: &mut Buf, pattern: &'a Pattern<'a>, indent: u16, parens: Parens) { - pattern.format_with_options(buf, parens, Newlines::No, indent); +pub fn fmt_pattern<'a>( + buf: &mut Buf, + pattern: &'a Pattern<'a>, + flags: &MigrationFlags, + indent: u16, + parens: Parens, +) { + pattern.format_with_options(buf, parens, Newlines::No, flags, indent); } impl<'a> Formattable for PatternAs<'a> { @@ -25,6 +31,7 @@ impl<'a> Formattable for PatternAs<'a> { buf: &mut Buf, _parens: Parens, _newlines: Newlines, + _flags: &MigrationFlags, indent: u16, ) { buf.indent(indent); @@ -93,8 +100,15 @@ impl<'a> Formattable for Pattern<'a> { } } - fn format_with_options(&self, buf: &mut Buf, parens: Parens, _newlines: Newlines, indent: u16) { - fmt_pattern_inner(self, buf, parens, indent, self.is_multiline()); + fn format_with_options( + &self, + buf: &mut Buf, + parens: Parens, + _newlines: Newlines, + flags: &MigrationFlags, + indent: u16, + ) { + fmt_pattern_inner(self, buf, parens, flags, indent, self.is_multiline()); } } @@ -102,6 +116,7 @@ fn fmt_pattern_inner( pat: &Pattern<'_>, buf: &mut Buf, parens: Parens, + flags: &MigrationFlags, indent: u16, outer_is_multiline: bool, ) { @@ -122,7 +137,7 @@ fn fmt_pattern_inner( match me.item { Identifier { ident: string } => { buf.indent(indent); - buf.push_str(string); + snakify_camel_ident(buf, string, flags); } Tag(name) | OpaqueRef(name) => { buf.indent(indent); @@ -154,7 +169,7 @@ fn fmt_pattern_inner( } } - fmt_pattern_inner(&pat.item, buf, Parens::InApply, indent, is_multiline); + fmt_pattern_inner(&pat.item, buf, Parens::InApply, flags, indent, is_multiline); if !pat.after.is_empty() { if !is_multiline { @@ -170,6 +185,7 @@ fn fmt_pattern_inner( &loc_arg.value, buf, Parens::InApply, + flags, indent_more, is_multiline, ); @@ -197,7 +213,14 @@ fn fmt_pattern_inner( } } - fmt_pattern_inner(&item.item, buf, Parens::NotNeeded, indent, is_multiline); + fmt_pattern_inner( + &item.item, + buf, + Parens::NotNeeded, + flags, + indent, + is_multiline, + ); let is_multiline = item.item.is_multiline(); @@ -227,13 +250,14 @@ fn fmt_pattern_inner( RequiredField(name, loc_pattern) => { buf.indent(indent); - buf.push_str(name); + snakify_camel_ident(buf, name, flags); buf.push_str(":"); buf.spaces(1); fmt_pattern_inner( &loc_pattern.value, buf, Parens::NotNeeded, + flags, indent, is_multiline, ); @@ -241,10 +265,10 @@ fn fmt_pattern_inner( OptionalField(name, loc_pattern) => { buf.indent(indent); - buf.push_str(name); + snakify_camel_ident(buf, name, flags); buf.push_str(" ?"); buf.spaces(1); - loc_pattern.format(buf, indent); + loc_pattern.format(buf, flags, indent); } NumLiteral(string) => { @@ -274,7 +298,7 @@ fn fmt_pattern_inner( buf.indent(indent); buf.push_str(string); } - StrLiteral(literal) => fmt_str_literal(buf, literal, indent), + StrLiteral(literal) => fmt_str_literal(buf, literal, flags, indent), SingleQuote(string) => { buf.indent(indent); format_sq_literal(buf, string); @@ -294,6 +318,7 @@ fn fmt_pattern_inner( &loc_pattern.value, buf, Parens::NotNeeded, + flags, indent, is_multiline, ); @@ -318,6 +343,7 @@ fn fmt_pattern_inner( &loc_pattern.value, buf, Parens::NotNeeded, + flags, indent, is_multiline, ); @@ -340,7 +366,7 @@ fn fmt_pattern_inner( // these spaces "belong" to the `..`, which can never be multiline fmt_comments_only(buf, list_rest_spaces.iter(), NewlineAt::Bottom, indent); - pattern_as.format(buf, indent + INDENT); + pattern_as.format(buf, flags, indent + INDENT); } } @@ -352,9 +378,9 @@ fn fmt_pattern_inner( buf.push('('); } - fmt_pattern(buf, &pattern.value, indent, parens); + fmt_pattern(buf, &pattern.value, flags, indent, parens); - pattern_as.format(buf, indent + INDENT); + pattern_as.format(buf, flags, indent + INDENT); if needs_parens { buf.indent(indent); @@ -376,7 +402,7 @@ fn fmt_pattern_inner( buf.push('.'); } - buf.push_str(ident); + snakify_camel_ident(buf, ident, flags); } } @@ -500,3 +526,91 @@ pub fn pattern_lift_spaces_after<'a, 'b: 'a>( after: lifted.after, } } +fn snakify_camel_ident(buf: &mut Buf, string: &str, flags: &MigrationFlags) { + let chars: Vec = string.chars().collect(); + if !flags.snakify || (string.contains('_') && !string.ends_with('_')) { + buf.push_str(string); + return; + } + let mut index = 0; + let len = chars.len(); + + while index < len { + let prev = if index == 0 { + None + } else { + Some(chars[index - 1]) + }; + let c = chars[index]; + let next = chars.get(index + 1); + let boundary = match (prev, c, next) { + // LUU, LUN, and LUL (simplified to LU_) + (Some(p), curr, _) if !p.is_ascii_uppercase() && curr.is_ascii_uppercase() => true, + // UUL + (Some(p), curr, Some(n)) + if p.is_ascii_uppercase() + && curr.is_ascii_uppercase() + && n.is_ascii_lowercase() => + { + true + } + _ => false, + }; + // those are boundary transitions - should push _ and curr + if boundary { + buf.push('_'); + } + buf.push(c.to_ascii_lowercase()); + index += 1; + } +} + +#[cfg(test)] +mod snakify_test { + use bumpalo::Bump; + + use super::snakify_camel_ident; + use crate::{annotation::MigrationFlags, Buf}; + + fn check_snakify(arena: &Bump, original: &str) -> String { + let mut buf = Buf::new_in(arena); + buf.indent(0); + let flags = MigrationFlags::new(true); + snakify_camel_ident(&mut buf, original, &flags); + buf.text.to_string() + } + + #[test] + fn test_snakify_camel_ident() { + let arena = Bump::new(); + assert_eq!(check_snakify(&arena, "A"), "a"); + assert_eq!(check_snakify(&arena, "Ba"), "ba"); + assert_eq!(check_snakify(&arena, "aB"), "a_b"); + assert_eq!(check_snakify(&arena, "aBa"), "a_ba"); + assert_eq!(check_snakify(&arena, "mBB"), "m_bb"); + assert_eq!(check_snakify(&arena, "NbA"), "nb_a"); + assert_eq!(check_snakify(&arena, "doIT"), "do_it"); + assert_eq!(check_snakify(&arena, "ROC"), "roc"); + assert_eq!( + check_snakify(&arena, "someHTTPRequest"), + "some_http_request" + ); + assert_eq!(check_snakify(&arena, "usingXML"), "using_xml"); + assert_eq!(check_snakify(&arena, "some123"), "some123"); + assert_eq!( + check_snakify(&arena, "theHTTPStatus404"), + "the_http_status404" + ); + assert_eq!( + check_snakify(&arena, "inThe99thPercentile"), + "in_the99th_percentile" + ); + assert_eq!( + check_snakify(&arena, "all400SeriesErrorCodes"), + "all400_series_error_codes", + ); + assert_eq!(check_snakify(&arena, "number4Yellow"), "number4_yellow"); + assert_eq!(check_snakify(&arena, "useCases4Cobol"), "use_cases4_cobol"); + assert_eq!(check_snakify(&arena, "c3PO"), "c3_po") + } +} diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 82bb13101c..cb43891e16 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -6019,25 +6019,6 @@ All branches in an `if` must have the same type! " ); - test_report!( - closure_underscore_ident, - indoc!( - r" - \the_answer -> 100 - " - ), - @r" - ── NAMING PROBLEM in /code/proj/Main.roc ─────────────────────────────────────── - - I am trying to parse an identifier here: - - 4│ \the_answer -> 100 - ^ - - Underscores are not allowed in identifiers. Use camelCase instead! - " - ); - test_report!( #[ignore] double_binop, @@ -10697,26 +10678,26 @@ All branches in an `if` must have the same type! ); test_report!( - underscore_in_middle_of_identifier, + call_with_declared_identifier_with_more_than_one_underscore, indoc!( r" - f = \x, y, z -> x + y + z + f__arg = \x, y, z -> x + y + z - \a, _b -> f a var_name 1 + \a, b -> f__arg a b 1 " ), |golden| pretty_assertions::assert_eq!( golden, indoc!( - r" - ── SYNTAX PROBLEM in /code/proj/Main.roc ─────────────────────────────────────── + r"── NAMING PROBLEM in /code/proj/Main.roc ─────────────────────────────────────── - Underscores are not allowed in identifier names: + I am trying to parse an identifier here: - 6│ \a, _b -> f a var_name 1 - ^^^^^^^^ + 4│ f__arg = \x, y, z -> x + y + z + ^^^^^^ - I recommend using camelCase. It's the standard style in Roc code! + While snake case is allowed here, only a single consecutive underscore + should be used. " ), ) diff --git a/crates/compiler/parse/src/ident.rs b/crates/compiler/parse/src/ident.rs index dae3f1b015..0641ed7fc6 100644 --- a/crates/compiler/parse/src/ident.rs +++ b/crates/compiler/parse/src/ident.rs @@ -231,6 +231,7 @@ pub enum BadIdent { UnderscoreAlone(Position), UnderscoreInMiddle(Position), + TooManyUnderscores(Position), UnderscoreAtStart { position: Position, /// If this variable was already declared in a pattern (e.g. \_x -> _x), @@ -252,11 +253,21 @@ fn is_alnum(ch: char) -> bool { } fn chomp_lowercase_part(buffer: &[u8]) -> Result<&str, Progress> { - chomp_part(char::is_lowercase, is_alnum, true, buffer) + chomp_part( + char::is_lowercase, + is_plausible_ident_continue, + true, + buffer, + ) } fn chomp_uppercase_part(buffer: &[u8]) -> Result<&str, Progress> { - chomp_part(char::is_uppercase, is_alnum, false, buffer) + chomp_part( + char::is_uppercase, + is_plausible_ident_continue, + false, + buffer, + ) } fn chomp_anycase_part(buffer: &[u8]) -> Result<&str, Progress> { @@ -265,7 +276,12 @@ fn chomp_anycase_part(buffer: &[u8]) -> Result<&str, Progress> { let allow_bang = char::from_utf8_slice_start(buffer).map_or(false, |(leading, _)| leading.is_lowercase()); - chomp_part(char::is_alphabetic, is_alnum, allow_bang, buffer) + chomp_part( + char::is_alphabetic, + is_plausible_ident_continue, + allow_bang, + buffer, + ) } fn chomp_integer_part(buffer: &[u8]) -> Result<&str, Progress> { @@ -429,7 +445,14 @@ fn chomp_opaque_ref(buffer: &[u8], pos: Position) -> Result<&str, BadIdent> { Err(bad_ident(pos.bump_column(width as u32))) } else { let value = unsafe { std::str::from_utf8_unchecked(&buffer[..width]) }; - Ok(value) + if value.contains('_') { + // we don't allow underscores in the middle of an identifier + // but still parse them (and generate a malformed identifier) + // to give good error messages for this case + Err(BadIdent::UnderscoreInMiddle(pos.bump_column(width as u32))) + } else { + Ok(value) + } } } Err(_) => Err(bad_ident(pos.bump_column(1))), @@ -486,7 +509,7 @@ fn chomp_identifier_chain<'a>( } while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) { - if ch.is_alphabetic() || ch.is_ascii_digit() { + if ch.is_alphabetic() || ch.is_ascii_digit() || ch == '_' { chomped += width; } else if ch == '!' && !first_is_uppercase { chomped += width; @@ -556,19 +579,20 @@ fn chomp_identifier_chain<'a>( BadIdent::WeirdDotAccess(pos.bump_column(chomped as u32 + width)), )), } - } else if let Ok(('_', _)) = char::from_utf8_slice_start(&buffer[chomped..]) { - // we don't allow underscores in the middle of an identifier - // but still parse them (and generate a malformed identifier) - // to give good error messages for this case - Err(( - chomped as u32 + 1, - BadIdent::UnderscoreInMiddle(pos.bump_column(chomped as u32 + 1)), - )) } else if first_is_uppercase { // just one segment, starting with an uppercase letter; that's a tag let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; - - Ok((chomped as u32, Ident::Tag(value))) + if value.contains('_') { + // we don't allow underscores in the middle of an identifier + // but still parse them (and generate a malformed identifier) + // to give good error messages for this case + Err(( + chomped as u32, + BadIdent::UnderscoreInMiddle(pos.bump_column(chomped as u32)), + )) + } else { + Ok((chomped as u32, Ident::Tag(value))) + } } else { // just one segment, starting with a lowercase letter; that's a normal identifier let value = unsafe { std::str::from_utf8_unchecked(&buffer[..chomped]) }; @@ -689,3 +713,121 @@ fn chomp_access_chain<'a>(buffer: &'a [u8], parts: &mut Vec<'a, Accessor<'a>>) - Ok(chomped as u32) } } + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_ident_parses<'a>(arena: &'a Bump, ident: &str, expected: Ident<'a>) { + let s = State::new(ident.as_bytes()); + let (_, id, _) = parse_ident(arena, s, 0).unwrap(); + assert_eq!(id, expected); + } + + fn assert_ident_parses_tag(arena: &Bump, ident: &str) { + assert_ident_parses(arena, ident, Ident::Tag(ident)); + } + fn assert_ident_parses_opaque(arena: &Bump, ident: &str) { + assert_ident_parses(arena, ident, Ident::OpaqueRef(ident)); + } + fn assert_ident_parses_simple_access(arena: &Bump, ident: &str) { + assert_ident_parses( + arena, + ident, + Ident::Access { + module_name: "", + parts: arena.alloc([Accessor::RecordField(ident)]), + }, + ); + } + + fn assert_ident_parses_malformed(arena: &Bump, ident: &str, pos: Position) { + assert_ident_parses( + arena, + ident, + Ident::Malformed(ident, BadIdent::UnderscoreInMiddle(pos)), + ); + } + + #[test] + fn test_parse_ident_lowercase_camel() { + let arena = Bump::new(); + assert_ident_parses_simple_access(&arena, "hello"); + assert_ident_parses_simple_access(&arena, "hello23"); + assert_ident_parses_simple_access(&arena, "helloWorld"); + assert_ident_parses_simple_access(&arena, "helloWorld23"); + assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag"); + assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag_"); + assert_ident_parses_simple_access(&arena, "helloworldthisisquiteatag_"); + assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag23"); + assert_ident_parses_simple_access(&arena, "helloWorldThisIsQuiteATag23_"); + assert_ident_parses_simple_access(&arena, "helloworldthisisquiteatag23_"); + } + + #[test] + fn test_parse_ident_lowercase_snake() { + let arena = Bump::new(); + assert_ident_parses_simple_access(&arena, "hello_world"); + assert_ident_parses_simple_access(&arena, "hello_world23"); + assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_tag"); + assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_tag_"); + assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_tag23"); + assert_ident_parses_simple_access(&arena, "hello_world_this_is_quite_a_tag23_"); + } + + #[test] + fn test_parse_tag_camel() { + let arena = Bump::new(); + assert_ident_parses_tag(&arena, "Hello"); + assert_ident_parses_tag(&arena, "Hello23"); + assert_ident_parses_tag(&arena, "HelloWorld"); + assert_ident_parses_tag(&arena, "HelloWorld23"); + assert_ident_parses_tag(&arena, "HelloWorldThisIsQuiteATag"); + assert_ident_parses_tag(&arena, "HelloWorldThisIsQuiteATag23"); + } + + #[test] + fn test_parse_tag_snake_is_malformed() { + let arena = Bump::new(); + assert_ident_parses_malformed(&arena, "Hello_World", Position { offset: 11 }); + assert_ident_parses_malformed(&arena, "Hello_World23", Position { offset: 13 }); + assert_ident_parses_malformed( + &arena, + "Hello_World_This_Is_Quite_A_Tag", + Position { offset: 31 }, + ); + assert_ident_parses_malformed( + &arena, + "Hello_World_This_Is_Quite_A_Tag23", + Position { offset: 33 }, + ); + } + + #[test] + fn test_parse_opaque_ref_camel() { + let arena = Bump::new(); + assert_ident_parses_opaque(&arena, "@Hello"); + assert_ident_parses_opaque(&arena, "@Hello23"); + assert_ident_parses_opaque(&arena, "@HelloWorld"); + assert_ident_parses_opaque(&arena, "@HelloWorld23"); + assert_ident_parses_opaque(&arena, "@HelloWorldThisIsQuiteARef"); + assert_ident_parses_opaque(&arena, "@HelloWorldThisIsQuiteARef23"); + } + + #[test] + fn test_parse_opaque_ref_snake_is_malformed() { + let arena = Bump::new(); + assert_ident_parses_malformed(&arena, "@Hello_World", Position { offset: 12 }); + assert_ident_parses_malformed(&arena, "@Hello_World23", Position { offset: 14 }); + assert_ident_parses_malformed( + &arena, + "@Hello_World_This_Is_Quite_A_Ref", + Position { offset: 32 }, + ); + assert_ident_parses_malformed( + &arena, + "@Hello_World_This_Is_Quite_A_Ref23", + Position { offset: 34 }, + ); + } +} diff --git a/crates/compiler/parse/src/normalize.rs b/crates/compiler/parse/src/normalize.rs index 3de850a216..021e44cd85 100644 --- a/crates/compiler/parse/src/normalize.rs +++ b/crates/compiler/parse/src/normalize.rs @@ -855,6 +855,7 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent { position: Position::zero(), declaration_region, }, + BadIdent::TooManyUnderscores(_) => BadIdent::TooManyUnderscores(Position::zero()), BadIdent::QualifiedTag(_) => BadIdent::QualifiedTag(Position::zero()), BadIdent::WeirdAccessor(_) => BadIdent::WeirdAccessor(Position::zero()), BadIdent::WeirdDotAccess(_) => BadIdent::WeirdDotAccess(Position::zero()), diff --git a/crates/compiler/problem/src/can.rs b/crates/compiler/problem/src/can.rs index 73496278f7..7cea9be6a9 100644 --- a/crates/compiler/problem/src/can.rs +++ b/crates/compiler/problem/src/can.rs @@ -763,8 +763,8 @@ impl RuntimeError { record: _, field: region, } - | RuntimeError::ReadIngestedFileError { region, .. } => *region, - RuntimeError::InvalidUnicodeCodePt(region) => *region, + | RuntimeError::ReadIngestedFileError { region, .. } + | RuntimeError::InvalidUnicodeCodePt(region) => *region, RuntimeError::UnresolvedTypeVar | RuntimeError::ErroneousType => Region::zero(), RuntimeError::LookupNotInScope { loc_name, .. } => loc_name.region, RuntimeError::OpaqueNotDefined { usage, .. } => usage.region, diff --git a/crates/compiler/specialize_types/src/specialize_type.rs b/crates/compiler/specialize_types/src/specialize_type.rs index 5e5d6fbed1..780a141fa5 100644 --- a/crates/compiler/specialize_types/src/specialize_type.rs +++ b/crates/compiler/specialize_types/src/specialize_type.rs @@ -452,12 +452,11 @@ fn number_args_to_mono_id( // Unroll aliases in this loop, as many aliases as we encounter. loop { match content { - Content::Structure(flat_type) => { - if let FlatType::Apply(outer_symbol, args) = flat_type { - return num_num_args_to_mono_id(*outer_symbol, *args, subs, problems); - } else { - break; - } + Content::Structure(FlatType::Apply(outer_symbol, args)) => { + return num_num_args_to_mono_id(*outer_symbol, *args, subs, problems); + } + Content::Structure(_) => { + break; } Content::FlexVar(_) => { // Num * diff --git a/crates/compiler/test_syntax/src/test_helpers.rs b/crates/compiler/test_syntax/src/test_helpers.rs index 187c2e32da..0c5159f79d 100644 --- a/crates/compiler/test_syntax/src/test_helpers.rs +++ b/crates/compiler/test_syntax/src/test_helpers.rs @@ -1,5 +1,5 @@ use bumpalo::Bump; -use roc_fmt::{annotation::Formattable, header::fmt_header}; +use roc_fmt::{annotation::Formattable, annotation::MigrationFlags, header::fmt_header}; use roc_parse::{ ast::{Defs, Expr, FullAst, Header, Malformed, SpacesBefore}, header::parse_module_defs, @@ -83,24 +83,25 @@ impl<'a> Output<'a> { pub fn format(&self) -> InputOwned { let arena = Bump::new(); let mut buf = Buf::new_in(&arena); + let flags = MigrationFlags::new(false); match self { Output::Header(header) => { - fmt_header(&mut buf, header); + fmt_header(&mut buf, header, &flags); buf.fmt_end_of_file(); InputOwned::Header(buf.as_str().to_string()) } Output::ModuleDefs(defs) => { - defs.format(&mut buf, 0); + defs.format(&mut buf, &flags, 0); buf.fmt_end_of_file(); InputOwned::ModuleDefs(buf.as_str().to_string()) } Output::Expr(expr) => { - expr.format(&mut buf, 0); + expr.format(&mut buf, &flags, 0); InputOwned::Expr(buf.as_str().to_string()) } Output::Full(full) => { - fmt_header(&mut buf, &full.header); - full.defs.format(&mut buf, 0); + fmt_header(&mut buf, &full.header, &flags); + full.defs.format(&mut buf, &flags, 0); buf.fmt_end_of_file(); InputOwned::Full(buf.as_str().to_string()) } diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.result-ast deleted file mode 100644 index 1544a84253..0000000000 --- a/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.result-ast +++ /dev/null @@ -1,13 +0,0 @@ -Closure( - [ - @1-11 MalformedIdent( - "the_answer", - UnderscoreInMiddle( - @5, - ), - ), - ], - @15-17 Num( - "42", - ), -) diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast deleted file mode 100644 index c292137433..0000000000 --- a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.result-ast +++ /dev/null @@ -1,42 +0,0 @@ -Defs( - Defs { - tags: [ - EitherIndex(0), - ], - regions: [ - @0-3, - ], - space_before: [ - Slice { start: 0, length: 0 }, - ], - space_after: [ - Slice { start: 0, length: 0 }, - ], - spaces: [], - type_defs: [ - Alias { - header: TypeHeader { - name: @0-1 "J", - vars: [], - }, - ann: @2-3 Apply( - "", - "R", - [], - ), - }, - ], - value_defs: [], - }, - @5-8 SpaceBefore( - MalformedIdent( - "n_p", - UnderscoreInMiddle( - @7, - ), - ), - [ - Newline, - ], - ), -) diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/malformed/malformed_ident_due_to_underscore.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.result-ast new file mode 100644 index 0000000000..3577776020 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.result-ast @@ -0,0 +1,15 @@ +SpaceAfter( + Closure( + [ + @1-11 Identifier { + ident: "the_answer", + }, + ], + @15-17 Num( + "42", + ), + ), + [ + Newline, + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.roc new file mode 100644 index 0000000000..21ac2bc0aa --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/single_arg_with_underscore_closure.expr.roc @@ -0,0 +1 @@ +\the_answer -> 42 diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.formatted.roc b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.formatted.roc similarity index 100% rename from crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.formatted.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.formatted.roc diff --git a/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.result-ast b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.result-ast new file mode 100644 index 0000000000..c83b3606c4 --- /dev/null +++ b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.result-ast @@ -0,0 +1,45 @@ +SpaceAfter( + Defs( + Defs { + tags: [ + EitherIndex(0), + ], + regions: [ + @0-3, + ], + space_before: [ + Slice { start: 0, length: 0 }, + ], + space_after: [ + Slice { start: 0, length: 0 }, + ], + spaces: [], + type_defs: [ + Alias { + header: TypeHeader { + name: @0-1 "J", + vars: [], + }, + ann: @2-3 Apply( + "", + "R", + [], + ), + }, + ], + value_defs: [], + }, + @4-7 SpaceBefore( + Var { + module_name: "", + ident: "n_p", + }, + [ + Newline, + ], + ), + ), + [ + Newline, + ], +) diff --git a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.roc b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.roc similarity index 50% rename from crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.roc rename to crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.roc index fa050810cd..495974bd91 100644 --- a/crates/compiler/test_syntax/tests/snapshots/malformed/underscore_expr_in_def.expr.roc +++ b/crates/compiler/test_syntax/tests/snapshots/pass/underscore_expr_in_def.expr.roc @@ -1,2 +1,2 @@ J:R - n_p \ No newline at end of file +n_p diff --git a/crates/compiler/test_syntax/tests/test_fmt.rs b/crates/compiler/test_syntax/tests/test_fmt.rs index 89bb5a1ebd..f20bad2cbe 100644 --- a/crates/compiler/test_syntax/tests/test_fmt.rs +++ b/crates/compiler/test_syntax/tests/test_fmt.rs @@ -4,6 +4,7 @@ extern crate indoc; #[cfg(test)] mod test_fmt { use bumpalo::Bump; + use roc_fmt::annotation::MigrationFlags; use roc_fmt::def::fmt_defs; use roc_fmt::header::fmt_header; use roc_fmt::Buf; @@ -36,11 +37,12 @@ mod test_fmt { state: State<'a>, buf: &mut Buf<'_>, ) { - fmt_header(buf, header); + let flags = MigrationFlags::new(false); + fmt_header(buf, header, &flags); match parse_module_defs(arena, state, Defs::default()) { Ok(loc_defs) => { - fmt_defs(buf, &loc_defs, 0); + fmt_defs(buf, &loc_defs, &flags, 0); } Err(error) => { let src = if src.len() > 1000 { @@ -1492,7 +1494,7 @@ mod test_fmt { fn parenthetical_def() { expr_formats_same(indoc!( r" - (UserId userId) = 5 + (UserId user_id) = 5 y = 10 42 @@ -1502,7 +1504,7 @@ mod test_fmt { expr_formats_same(indoc!( r" # A - (UserId userId) = 5 + (UserId user_id) = 5 # B y = 10 @@ -1539,13 +1541,13 @@ mod test_fmt { fn lambda_returns_record() { expr_formats_same(indoc!( r" - toRecord = \_ -> { + to_record = \_ -> { x: 1, y: 2, z: 3, } - toRecord + to_record " )); @@ -1560,7 +1562,7 @@ mod test_fmt { expr_formats_same(indoc!( r" - toRecord = \_ -> + to_record = \_ -> val = 0 { @@ -1569,32 +1571,32 @@ mod test_fmt { z: 3, } - toRecord + to_record " )); expr_formats_to( indoc!( r" - toRecord = \_ -> + to_record = \_ -> { x: 1, y: 2, z: 3, } - toRecord + to_record " ), indoc!( r" - toRecord = \_ -> { + to_record = \_ -> { x: 1, y: 2, z: 3, } - toRecord + to_record " ), ); @@ -1604,13 +1606,13 @@ mod test_fmt { fn lambda_returns_list() { expr_formats_same(indoc!( r" - toList = \_ -> [ + to_list = \_ -> [ 1, 2, 3, ] - toList + to_list " )); @@ -1625,7 +1627,7 @@ mod test_fmt { expr_formats_same(indoc!( r" - toList = \_ -> + to_list = \_ -> val = 0 [ @@ -1634,32 +1636,32 @@ mod test_fmt { 3, ] - toList + to_list " )); expr_formats_to( indoc!( r" - toList = \_ -> + to_list = \_ -> [ 1, 2, 3, ] - toList + to_list " ), indoc!( r" - toList = \_ -> [ + to_list = \_ -> [ 1, 2, 3, ] - toList + to_list " ), ); @@ -3116,7 +3118,7 @@ mod test_fmt { expr_formats_same(indoc!( r" - myDef = + my_def = list = [ a, b, @@ -3127,7 +3129,7 @@ mod test_fmt { d, } - myDef + my_def " )); @@ -3672,7 +3674,7 @@ mod test_fmt { fn def_when() { expr_formats_same(indoc!( r" - myLongFunctionName = \x -> + my_long_function_name = \x -> when b is 1 | 2 -> when c is @@ -4974,10 +4976,10 @@ mod test_fmt { exposes [] packages {} imports [] - provides [ mainForHost ] + provides [ main_for_host ] - mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } - mainForHost = main + main_for_host : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } + main_for_host = main "# ), indoc!( @@ -4987,10 +4989,10 @@ mod test_fmt { exposes [] packages {} imports [] - provides [mainForHost] + provides [main_for_host] - mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } - mainForHost = main + main_for_host : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View } + main_for_host = main "# ), ); @@ -5313,8 +5315,8 @@ mod test_fmt { fn backpassing_simple() { expr_formats_same(indoc!( r" - getChar = \ctx -> - x <- Task.await (getCharScope scope) + get_char = \ctx -> + x <- Task.await (get_char_scope scope) 42 42 @@ -5326,8 +5328,8 @@ mod test_fmt { fn backpassing_apply_tag() { expr_formats_same(indoc!( r" - getChar = \ctx -> - (T val newScope) <- Task.await (getCharScope scope) + get_char = \ctx -> + (T val new_scope) <- Task.await (get_char_scope scope) 42 42 @@ -5406,9 +5408,9 @@ mod test_fmt { fn backpassing_body_on_newline() { expr_formats_same(indoc!( r" - getChar = \ctx -> + get_char = \ctx -> x <- - Task.await (getCharScope scope) + Task.await (get_char_scope scope) 42 42 diff --git a/crates/compiler/test_syntax/tests/test_snapshots.rs b/crates/compiler/test_syntax/tests/test_snapshots.rs index 15eb1f24ec..7378a2046b 100644 --- a/crates/compiler/test_syntax/tests/test_snapshots.rs +++ b/crates/compiler/test_syntax/tests/test_snapshots.rs @@ -265,12 +265,10 @@ mod test_snapshots { fail/where_type_variable.expr, fail/wild_case_arrow.expr, malformed/bad_opaque_ref.expr, - malformed/malformed_ident_due_to_underscore.expr, malformed/malformed_pattern_field_access.expr, // See https://github.com/roc-lang/roc/issues/399 malformed/malformed_pattern_module_name.expr, // See https://github.com/roc-lang/roc/issues/399 malformed/module_dot_tuple.expr, malformed/qualified_tag.expr, - malformed/underscore_expr_in_def.expr, pass/ability_demand_signature_is_multiline.expr, pass/ability_multi_line.expr, pass/ability_single_line.expr, @@ -575,6 +573,7 @@ mod test_snapshots { pass/return_with_after.expr, pass/separate_defs.moduledefs, pass/single_arg_closure.expr, + pass/single_arg_with_underscore_closure.expr, pass/single_underscore_closure.expr, pass/space_before_colon.full, pass/space_before_parens_space_after.expr, @@ -627,6 +626,7 @@ mod test_snapshots { pass/unary_not.expr, pass/unary_not_with_parens.expr, pass/underscore_backpassing.expr, + pass/underscore_expr_in_def.expr, pass/underscore_in_assignment_pattern.expr, pass/value_def_confusion.expr, pass/var_else.expr, diff --git a/crates/language_server/src/analysis/analysed_doc.rs b/crates/language_server/src/analysis/analysed_doc.rs index 162557577c..f7fd703b99 100644 --- a/crates/language_server/src/analysis/analysed_doc.rs +++ b/crates/language_server/src/analysis/analysed_doc.rs @@ -1,4 +1,5 @@ use log::{debug, info}; +use roc_fmt::annotation::MigrationFlags; use std::collections::HashMap; use bumpalo::Bump; @@ -92,7 +93,8 @@ impl DocInfo { let arena = &Bump::new(); let ast = Ast::parse(arena, source).ok()?; - let fmt = ast.fmt(); + let flags = MigrationFlags::new(false); + let fmt = ast.fmt(&flags); if source == fmt.as_str() { None diff --git a/crates/language_server/src/analysis/parse_ast.rs b/crates/language_server/src/analysis/parse_ast.rs index aeac795765..0f2965fa72 100644 --- a/crates/language_server/src/analysis/parse_ast.rs +++ b/crates/language_server/src/analysis/parse_ast.rs @@ -1,5 +1,5 @@ use bumpalo::Bump; -use roc_fmt::Buf; +use roc_fmt::{annotation::MigrationFlags, Buf}; use roc_parse::{ ast::{Defs, Header, SpacesBefore}, header::parse_module_defs, @@ -40,12 +40,12 @@ impl<'a> Ast<'a> { }) } - pub fn fmt(&self) -> FormattedAst<'a> { + pub fn fmt(&self, flags: &MigrationFlags) -> FormattedAst<'a> { let mut buf = Buf::new_in(self.arena); - roc_fmt::header::fmt_header(&mut buf, &self.module); + roc_fmt::header::fmt_header(&mut buf, &self.module, flags); - roc_fmt::def::fmt_defs(&mut buf, &self.defs, 0); + roc_fmt::def::fmt_defs(&mut buf, &self.defs, flags, 0); buf.fmt_end_of_file(); diff --git a/crates/repl_eval/src/gen.rs b/crates/repl_eval/src/gen.rs index 296e8222fe..f1dd8091ae 100644 --- a/crates/repl_eval/src/gen.rs +++ b/crates/repl_eval/src/gen.rs @@ -25,8 +25,9 @@ pub fn format_answer<'a>(arena: &'a Bump, answer: Expr<'_>) -> &'a str { Expr::Closure(_, _) => "", _ => { let mut expr = roc_fmt::Buf::new_in(arena); + let flags = roc_fmt::annotation::MigrationFlags::new(false); - answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, 0); + answer.format_with_options(&mut expr, Parens::NotNeeded, Newlines::Yes, &flags, 0); expr.into_bump_str() } diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index eed1ee9b03..e3575ff71a 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -1649,10 +1649,10 @@ fn to_bad_ident_expr_report<'b>( UnderscoreInMiddle(_pos) => { alloc.stack([ - alloc.reflow("Underscores are not allowed in identifier names:"), + alloc.reflow("Underscores are not allowed in tag or opaque ref names:"), alloc.region(lines.convert_region(surroundings), severity), alloc.concat([alloc - .reflow(r"I recommend using camelCase. It's the standard style in Roc code!")]), + .reflow(r"I recommend using PascalCase. It's the standard style in Roc code!")]), ]) } @@ -1681,6 +1681,16 @@ fn to_bad_ident_expr_report<'b>( ]) } + TooManyUnderscores(_pos) => { + alloc.stack([ + alloc.reflow("This variable's name is using snake case, but has more than one consecutive underscore ('_') characters."), + alloc.region(lines.convert_region(surroundings), severity), + alloc.concat([ + alloc.reflow(r"When using snake case, Roc style recommends only using a single underscore consecutively. This will be fixed by the formatter.") + ]), + ]) + } + BadOpaqueRef(pos) => { use BadIdentNext::*; let kind = "an opaque reference"; @@ -1849,18 +1859,28 @@ fn to_bad_ident_pattern_report<'b>( ) } + TooManyUnderscores(_pos) => { + alloc.stack([ + alloc.reflow("I am trying to parse an identifier here:"), + alloc.region(lines.convert_region(surroundings), severity), + alloc.concat([ + alloc.reflow(r"While snake case is allowed here, only a single consecutive underscore should be used.") + ]), + ]) + } + UnderscoreInMiddle(pos) => { let region = Region::from_pos(pos.sub(1)); alloc.stack([ - alloc.reflow("I am trying to parse an identifier here:"), + alloc.reflow("I am trying to parse a tag or opaque ref here:"), alloc.region_with_subregion( lines.convert_region(surroundings), lines.convert_region(region), severity, ), alloc.concat([alloc.reflow( - r"Underscores are not allowed in identifiers. Use camelCase instead!", + r"Underscores are not allowed in tags or opaque refs. Use PascalCase instead!", )]), ]) } diff --git a/crates/reporting/src/error/expect.rs b/crates/reporting/src/error/expect.rs index c0ff888b3c..3ff504a795 100644 --- a/crates/reporting/src/error/expect.rs +++ b/crates/reporting/src/error/expect.rs @@ -1,6 +1,7 @@ use std::path::PathBuf; use bumpalo::Bump; +use roc_fmt::annotation::MigrationFlags; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_parse::ast::Expr; use roc_problem::Severity; @@ -58,7 +59,7 @@ impl<'a> Renderer<'a> { use roc_fmt::annotation::Formattable; let mut buf = roc_fmt::Buf::new_in(self.arena); - expr.format(&mut buf, 0); + expr.format(&mut buf, &MigrationFlags::new(false), 0); self.alloc.vcat([ self.alloc @@ -204,7 +205,7 @@ impl<'a> Renderer<'a> { let mut buf = roc_fmt::Buf::new_in(self.arena); { use roc_fmt::annotation::Formattable; - expr.format(&mut buf, 0); + expr.format(&mut buf, &MigrationFlags::new(false), 0); } writeln!(writer, "{}", buf.as_str())