Several migration fixes and make some zig parser improvements based on migrated code in the wild (#7716)

This commit is contained in:
Joshua Warner 2025-04-08 04:46:44 -07:00 committed by GitHub
parent 68af4b9a06
commit 6d22c4dd7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
329 changed files with 1663 additions and 833 deletions

View file

@ -9,6 +9,7 @@ use roc_can::expr::{DeclarationTag, Declarations, Expr};
use roc_error_macros::{internal_error, user_error}; use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_defs; use roc_fmt::def::fmt_defs;
use roc_fmt::header::fmt_header; use roc_fmt::header::fmt_header;
use roc_fmt::migrate::MigrateError;
use roc_fmt::Buf; use roc_fmt::Buf;
use roc_fmt::MigrationFlags; use roc_fmt::MigrationFlags;
use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadedModule, LoadingProblem, Threading}; use roc_load::{ExecutionMode, FunctionKind, LoadConfig, LoadedModule, LoadingProblem, Threading};
@ -78,7 +79,7 @@ fn is_roc_file(path: &Path) -> bool {
pub fn format_files( pub fn format_files(
files: std::vec::Vec<PathBuf>, files: std::vec::Vec<PathBuf>,
mode: FormatMode, mode: FormatMode,
flags: MigrationFlags, migrate: bool,
) -> Result<(), String> { ) -> Result<(), String> {
let arena = Bump::new(); let arena = Bump::new();
let mut files_to_reformat = Vec::new(); // to track which files failed `roc format --check` let mut files_to_reformat = Vec::new(); // to track which files failed `roc format --check`
@ -86,7 +87,7 @@ pub fn format_files(
for file in flatten_directories(files) { for file in flatten_directories(files) {
let src = std::fs::read_to_string(&file).unwrap(); let src = std::fs::read_to_string(&file).unwrap();
match format_src(&arena, &src, flags) { match format_src(&arena, &src, migrate) {
Ok(buf) => { Ok(buf) => {
match mode { match mode {
FormatMode::CheckOnly => { FormatMode::CheckOnly => {
@ -168,6 +169,11 @@ pub fn format_files(
unstable_2_file.display() unstable_2_file.display()
); );
} }
FormatProblem::UnsupportedMigration(e) => internal_error!(
"Formatting bug; unsupported migration\n\n\
Migration error was: {:?}\n\n",
e
),
}, },
} }
} }
@ -197,13 +203,28 @@ pub enum FormatProblem {
formatted_src: String, formatted_src: String,
reformatted_src: String, reformatted_src: String,
}, },
UnsupportedMigration(MigrateError),
} }
pub fn format_src(arena: &Bump, src: &str, flags: MigrationFlags) -> Result<String, FormatProblem> { pub fn format_src(arena: &Bump, src: &str, migrate: bool) -> Result<String, FormatProblem> {
let ast = arena.alloc(parse_all(arena, src).unwrap_or_else(|e| { let ast = arena.alloc(parse_all(arena, src).unwrap_or_else(|e| {
user_error!("Unexpected parse failure when parsing this formatting:\n\n{src}\n\nParse error was:\n\n{:#?}\n\n", e) user_error!("Unexpected parse failure when parsing this formatting:\n\n{src}\n\nParse error was:\n\n{:#?}\n\n", e)
})); }));
let flags = MigrationFlags {
snakify: migrate,
parens_and_commas: migrate,
};
let mut buf = Buf::new_in(arena, flags); let mut buf = Buf::new_in(arena, flags);
if migrate {
roc_fmt::migrate::fmt_header(&mut buf, &ast.header)
.map_err(FormatProblem::UnsupportedMigration)?;
roc_fmt::migrate::fmt_defs(&mut buf, &ast.defs)
.map_err(FormatProblem::UnsupportedMigration)?;
buf.fmt_end_of_file();
return Ok(buf.as_str().to_string());
}
fmt_all(&mut buf, ast); fmt_all(&mut buf, ast);
let reparsed_ast = match arena.alloc(parse_all(arena, buf.as_str())) { let reparsed_ast = match arena.alloc(parse_all(arena, buf.as_str())) {
@ -224,9 +245,7 @@ pub fn format_src(arena: &Bump, src: &str, flags: MigrationFlags) -> Result<Stri
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same. // 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... // 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 // TODO: fix PartialEq impl on ast types
if !flags.at_least_one_active() if format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}") {
&& format!("{ast_normalized:?}") != format!("{reparsed_ast_normalized:?}")
{
return Err(FormatProblem::ReformattingChangedAst { return Err(FormatProblem::ReformattingChangedAst {
formatted_src: buf.as_str().to_string(), formatted_src: buf.as_str().to_string(),
ast_before: format!("{ast_normalized:#?}\n"), ast_before: format!("{ast_normalized:#?}\n"),
@ -235,7 +254,7 @@ pub fn format_src(arena: &Bump, src: &str, flags: MigrationFlags) -> Result<Stri
} }
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted // 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, flags); let mut reformatted_buf = Buf::new_in(arena, MigrationFlags::default());
fmt_all(&mut reformatted_buf, reparsed_ast); fmt_all(&mut reformatted_buf, reparsed_ast);
@ -483,12 +502,7 @@ main =
fn test_single_file_needs_reformatting() { fn test_single_file_needs_reformatting() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let file_path = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC); let file_path = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC);
let flags = MigrationFlags { let result = format_files(vec![file_path.clone()], FormatMode::CheckOnly, false);
snakify: false,
parens_and_commas: false,
};
let result = format_files(vec![file_path.clone()], FormatMode::CheckOnly, flags);
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err(), result.unwrap_err(),
@ -506,12 +520,7 @@ main =
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let file1 = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC); let file1 = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC);
let file2 = setup_test_file(dir.path(), "test2.roc", UNFORMATTED_ROC); let file2 = setup_test_file(dir.path(), "test2.roc", UNFORMATTED_ROC);
let flags = MigrationFlags { let result = format_files(vec![file1, file2], FormatMode::CheckOnly, false);
snakify: false,
parens_and_commas: false,
};
let result = format_files(vec![file1, file2], FormatMode::CheckOnly, flags);
assert!(result.is_err()); assert!(result.is_err());
let error_message = result.unwrap_err(); let error_message = result.unwrap_err();
assert!(error_message.contains("test1.roc") && error_message.contains("test2.roc")); assert!(error_message.contains("test1.roc") && error_message.contains("test2.roc"));
@ -523,12 +532,7 @@ main =
fn test_no_files_need_reformatting() { fn test_no_files_need_reformatting() {
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let file_path = setup_test_file(dir.path(), "formatted.roc", FORMATTED_ROC); let file_path = setup_test_file(dir.path(), "formatted.roc", FORMATTED_ROC);
let flags = MigrationFlags { let result = format_files(vec![file_path], FormatMode::CheckOnly, false);
snakify: false,
parens_and_commas: false,
};
let result = format_files(vec![file_path], FormatMode::CheckOnly, flags);
assert!(result.is_ok()); assert!(result.is_ok());
cleanup_temp_dir(dir); cleanup_temp_dir(dir);
@ -540,15 +544,10 @@ main =
let file_formatted = setup_test_file(dir.path(), "formatted.roc", FORMATTED_ROC); 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 file1_unformated = setup_test_file(dir.path(), "test1.roc", UNFORMATTED_ROC);
let file2_unformated = setup_test_file(dir.path(), "test2.roc", UNFORMATTED_ROC); let file2_unformated = setup_test_file(dir.path(), "test2.roc", UNFORMATTED_ROC);
let flags = MigrationFlags {
snakify: false,
parens_and_commas: false,
};
let result = format_files( let result = format_files(
vec![file_formatted, file1_unformated, file2_unformated], vec![file_formatted, file1_unformated, file2_unformated],
FormatMode::CheckOnly, FormatMode::CheckOnly,
flags, false,
); );
assert!(result.is_err()); assert!(result.is_err());
let error_message = result.unwrap_err(); let error_message = result.unwrap_err();

View file

@ -13,7 +13,6 @@ use roc_cli::{
}; };
use roc_docs::generate_docs_html; use roc_docs::generate_docs_html;
use roc_error_macros::{internal_error, user_error}; use roc_error_macros::{internal_error, user_error};
use roc_fmt::MigrationFlags;
use roc_gen_dev::AssemblyBackendMode; use roc_gen_dev::AssemblyBackendMode;
use roc_gen_llvm::llvm::build::LlvmBackendMode; use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{LoadingProblem, Threading}; use roc_load::{LoadingProblem, Threading};
@ -395,10 +394,6 @@ fn main() -> io::Result<()> {
false => FormatMode::WriteToFile, false => FormatMode::WriteToFile,
} }
}; };
let flags = MigrationFlags {
snakify: migrate,
parens_and_commas: migrate,
};
if from_stdin && matches!(format_mode, FormatMode::WriteToFile) { 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!)"); 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!)");
@ -451,7 +446,7 @@ fn main() -> io::Result<()> {
std::process::exit(1); std::process::exit(1);
}); });
match format_src(&arena, src, flags) { match format_src(&arena, src, migrate) {
Ok(formatted_src) => { Ok(formatted_src) => {
match format_mode { match format_mode {
FormatMode::CheckOnly => { FormatMode::CheckOnly => {
@ -483,7 +478,7 @@ fn main() -> io::Result<()> {
} }
} }
} else { } else {
match format_files(roc_files, format_mode, flags) { match format_files(roc_files, format_mode, migrate) {
Ok(()) => 0, Ok(()) => 0,
Err(message) => { Err(message) => {
eprintln!("{message}"); eprintln!("{message}");

View file

@ -24,7 +24,7 @@ pub struct Buf<'a> {
flags: MigrationFlags, flags: MigrationFlags,
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone, Default)]
pub struct MigrationFlags { pub struct MigrationFlags {
pub snakify: bool, pub snakify: bool,
pub parens_and_commas: bool, pub parens_and_commas: bool,

View file

@ -1,11 +1,13 @@
use roc_module::called_via::UnaryOp; use bumpalo::collections::String;
use bumpalo::Bump;
use roc_module::called_via::{Associativity, BinOp, UnaryOp};
use roc_parse::{ use roc_parse::{
ast::{ ast::{
AbilityImpls, AssignedField, Base, Collection, Defs, Expr, FunctionArrow, Header, AbilityImpls, AssignedField, Base, Collection, CommentOrNewline, Defs, Expr, ExtractSpaces,
ImplementsAbilities, ImplementsAbility, ImplementsClause, ImportAlias, ImportAsKeyword, FunctionArrow, Header, ImplementsAbilities, ImplementsAbility, ImplementsClause,
ImportExposingKeyword, ImportedModuleName, IngestedFileAnnotation, IngestedFileImport, ImportAlias, ImportAsKeyword, ImportExposingKeyword, ImportedModuleName,
ModuleImport, ModuleImportParams, Pattern, Spaced, Spaces, SpacesBefore, Tag, IngestedFileAnnotation, IngestedFileImport, ModuleImport, ModuleImportParams, Pattern,
TypeAnnotation, TypeDef, TypeHeader, TypeVar, ValueDef, Spaced, Spaces, SpacesBefore, Tag, TypeAnnotation, TypeDef, TypeHeader, TypeVar, ValueDef,
}, },
header::{ header::{
AppHeader, ExposedName, HostedHeader, Keyword, KeywordItem, ModuleHeader, ModuleName, AppHeader, ExposedName, HostedHeader, Keyword, KeywordItem, ModuleHeader, ModuleName,
@ -25,7 +27,7 @@ use crate::{
}, },
pattern::snakify_camel_ident, pattern::snakify_camel_ident,
spaces::fmt_spaces, spaces::fmt_spaces,
Buf, Buf, MigrationFlags,
}; };
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
@ -76,7 +78,7 @@ impl<F: Fmt> Fmt for AssignedField<'_, F> {
buf.indent(indent); buf.indent(indent);
buf.push_str(":"); buf.push_str(":");
buf.spaces(1); buf.spaces(1);
loc1.fmt(buf, indent, Suffix::None)?; loc1.fmt(buf, indent, suffix)?;
} }
AssignedField::OptionalValue(name, comment_or_newlines, loc1) => { AssignedField::OptionalValue(name, comment_or_newlines, loc1) => {
buf.indent(indent); buf.indent(indent);
@ -87,7 +89,7 @@ impl<F: Fmt> Fmt for AssignedField<'_, F> {
buf.indent(indent); buf.indent(indent);
buf.push_str(":"); buf.push_str(":");
buf.spaces(1); buf.spaces(1);
loc1.fmt(buf, indent, Suffix::None)?; loc1.fmt(buf, indent, suffix)?;
} }
AssignedField::IgnoredValue(name, comment_or_newlines, loc1) => { AssignedField::IgnoredValue(name, comment_or_newlines, loc1) => {
buf.indent(indent); buf.indent(indent);
@ -99,7 +101,7 @@ impl<F: Fmt> Fmt for AssignedField<'_, F> {
buf.indent(indent); buf.indent(indent);
buf.push_str(":"); buf.push_str(":");
buf.spaces(1); buf.spaces(1);
loc1.fmt(buf, indent, Suffix::None)?; loc1.fmt(buf, indent, suffix)?;
} }
AssignedField::LabelOnly(name) => { AssignedField::LabelOnly(name) => {
buf.indent(indent); buf.indent(indent);
@ -127,13 +129,13 @@ pub fn fmt_pattern(
match pat { match pat {
Pattern::Identifier { ident } => { Pattern::Identifier { ident } => {
buf.indent(indent); buf.indent(indent);
buf.push_str(ident); snakify_camel_ident(buf, ident);
} }
Pattern::QualifiedIdentifier { module_name, ident } => { Pattern::QualifiedIdentifier { module_name, ident } => {
buf.indent(indent); buf.indent(indent);
buf.push_str(module_name); buf.push_str(module_name);
buf.push('.'); buf.push('.');
buf.push_str(ident); snakify_camel_ident(buf, ident);
} }
Pattern::Tag(tag) => { Pattern::Tag(tag) => {
buf.indent(indent); buf.indent(indent);
@ -145,16 +147,31 @@ pub fn fmt_pattern(
} }
Pattern::Apply(loc, locs) => { Pattern::Apply(loc, locs) => {
fmt_pattern(buf, indent, &loc.value, Suffix::OpenRound)?; fmt_pattern(buf, indent, &loc.value, Suffix::OpenRound)?;
for loc in locs.iter() { for (i, loc) in locs.iter().enumerate() {
fmt_pattern(buf, indent, &loc.value, Suffix::Comma)?; let is_last = i == locs.len() - 1;
fmt_pattern(
buf,
indent,
&loc.value,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
} }
buf.indent(indent); buf.indent(indent);
buf.push(')'); buf.push(')');
} }
Pattern::PncApply(loc, collection) => { Pattern::PncApply(loc, collection) => {
fmt_pattern(buf, indent, &loc.value, Suffix::OpenRound)?; fmt_pattern(buf, indent, &loc.value, Suffix::OpenRound)?;
for loc in collection.iter() { for (i, loc) in collection.iter().enumerate() {
fmt_pattern(buf, indent, &loc.value, Suffix::Comma)?; let is_last = i == collection.len() - 1;
fmt_pattern(
buf,
indent,
&loc.value,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
if !collection.final_comments().is_empty() { if !collection.final_comments().is_empty() {
fmt_spaces(buf, collection.final_comments().iter(), indent); fmt_spaces(buf, collection.final_comments().iter(), indent);
@ -325,11 +342,7 @@ pub fn fmt_expr(
buf.push_str(module_name); buf.push_str(module_name);
buf.push('.'); buf.push('.');
} }
if buf.flags().snakify {
snakify_camel_ident(buf, ident); snakify_camel_ident(buf, ident);
} else {
buf.push_str(ident);
}
} }
Expr::Underscore(name) => { Expr::Underscore(name) => {
buf.indent(indent); buf.indent(indent);
@ -465,18 +478,34 @@ pub fn fmt_expr(
Expr::LowLevelDbg(..) => todo!(), Expr::LowLevelDbg(..) => todo!(),
Expr::Apply(func, args, _) => { Expr::Apply(func, args, _) => {
fmt_expr(buf, indent, &func.value, Suffix::OpenRound)?; fmt_expr(buf, indent, &func.value, Suffix::OpenRound)?;
for arg in *args { for (i, arg) in args.iter().enumerate() {
// TODO: make the suffix depend on whether we're multiline let is_last = i == args.len() - 1;
fmt_expr(buf, indent, &arg.value, Suffix::Comma)?; fmt_expr(
buf,
indent,
&arg.value,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
buf.indent(indent); buf.indent(indent);
buf.push(')'); buf.push(')');
} }
Expr::PncApply(func, collection) => { Expr::PncApply(func, collection) => {
fmt_expr(buf, indent, &func.value, Suffix::OpenRound)?; fmt_expr(buf, indent, &func.value, Suffix::OpenRound)?;
for arg in collection.iter() { for (i, arg) in collection.iter().enumerate() {
// TODO: make the suffix depend on whether we're multiline let is_last = i == collection.len() - 1;
fmt_expr(buf, indent, &arg.value, Suffix::Comma)?; fmt_expr(
buf,
indent,
&arg.value,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
if !collection.final_comments().is_empty() { if !collection.final_comments().is_empty() {
fmt_spaces(buf, collection.final_comments().iter(), indent); fmt_spaces(buf, collection.final_comments().iter(), indent);
@ -485,14 +514,9 @@ pub fn fmt_expr(
buf.push(')'); buf.push(')');
} }
Expr::BinOps(expr_op_pairs, last_expr) => { Expr::BinOps(expr_op_pairs, last_expr) => {
for (expr, op) in *expr_op_pairs { let arena = buf.text.bump();
fmt_expr(buf, indent, &expr.value, Suffix::None)?; let converted = migrate_pizza(buf, arena, expr_op_pairs, **last_expr)?;
buf.spaces(1); fmt_converted_ops(buf, indent, &converted, Suffix::None)?;
buf.indent(indent);
push_op(buf, op.value);
buf.spaces(1);
}
fmt_expr(buf, indent, &last_expr.value, Suffix::None)?;
} }
Expr::UnaryOp(expr, op) => { Expr::UnaryOp(expr, op) => {
buf.indent(indent); buf.indent(indent);
@ -528,10 +552,11 @@ pub fn fmt_expr(
} }
Expr::When(cond, when_branchs) => { Expr::When(cond, when_branchs) => {
buf.indent(indent); buf.indent(indent);
buf.push_str("when"); buf.push_str("match");
buf.spaces(1); buf.spaces(1);
fmt_expr(buf, indent, &cond.value, Suffix::None)?; fmt_expr(buf, indent, &cond.value, Suffix::None)?;
buf.indent(indent); buf.indent(indent);
buf.spaces(1);
buf.push('{'); buf.push('{');
buf.ensure_ends_with_newline(); buf.ensure_ends_with_newline();
for branch in when_branchs.iter() { for branch in when_branchs.iter() {
@ -599,6 +624,447 @@ pub fn fmt_expr(
Ok(()) Ok(())
} }
#[derive(Debug)]
enum MigratedBinOp<'a> {
BinOp {
lhs: &'a MigratedBinOp<'a>,
op: BinOp,
rhs: &'a MigratedBinOp<'a>,
},
Parens(&'a MigratedBinOp<'a>),
StaticDispatch {
lhs: &'a MigratedBinOp<'a>,
before: &'a [CommentOrNewline<'a>],
func: &'a str,
args: &'a [Expr<'a>],
after: &'a [CommentOrNewline<'a>],
},
FuncStaticDispatch {
lhs: &'a MigratedBinOp<'a>,
before: &'a [CommentOrNewline<'a>],
func: &'a Expr<'a>,
args: &'a [Expr<'a>],
after: &'a [CommentOrNewline<'a>],
},
Expr(Expr<'a>),
}
fn apply_ops<'a>(
buf: &Buf<'a>,
arena: &'a Bump,
stack: &mut Vec<MigratedBinOp<'a>>,
ops: &mut Vec<BinOp>,
min_precedence: u8,
) -> Result<(), MigrateError> {
while let Some(&op) = ops.last() {
if op.precedence() <= min_precedence
&& (op.associativity() == Associativity::RightAssociative
|| op.precedence() < min_precedence)
{
break;
}
ops.pop();
let rhs = stack.pop().unwrap();
let lhs = stack.pop().unwrap();
let result = match op {
BinOp::Pizza => {
// Need to migrate to StaticDispatch or FuncStaticDispatch!
match rhs {
MigratedBinOp::Expr(expr) => {
let expr = expr.extract_spaces();
match expr.item {
Expr::Apply(func, args, _) => {
if let Some(name) = is_staticable(buf, &func.value) {
MigratedBinOp::StaticDispatch {
lhs: maybe_parens(arena, lhs),
before: expr.before,
func: arena.alloc_str(name),
args: arena.alloc_slice_fill_iter(
args.iter().map(|arg| arg.value),
),
after: expr.after,
}
} else {
MigratedBinOp::FuncStaticDispatch {
lhs: maybe_parens(arena, lhs),
before: expr.before,
func: arena.alloc(func.value),
args: arena.alloc_slice_fill_iter(
args.iter().map(|arg| arg.value),
),
after: expr.after,
}
}
}
Expr::Var { module_name, ident } => {
let ident = if buf.flags.snakify {
snakify_camel_ident_in_bump(buf.text.bump(), ident)
.into_bump_str()
} else {
ident
};
if is_static_method(module_name, ident) {
MigratedBinOp::StaticDispatch {
lhs: maybe_parens(arena, lhs),
before: expr.before,
func: arena.alloc_str(ident),
args: &[],
after: expr.after,
}
} else {
MigratedBinOp::FuncStaticDispatch {
lhs: maybe_parens(arena, lhs),
before: expr.before,
func: arena.alloc(Expr::Var { module_name, ident }),
args: &[],
after: expr.after,
}
}
}
_ => return Err(MigrateError::PizzaOpRhsNotSupported),
}
}
_ => return Err(MigrateError::PizzaOpRhsNotSupported),
}
}
_ => MigratedBinOp::BinOp {
lhs: arena.alloc(lhs),
op,
rhs: arena.alloc(rhs),
},
};
stack.push(result);
}
Ok(())
}
fn maybe_parens<'a>(arena: &'a Bump, lhs: MigratedBinOp<'a>) -> &'a MigratedBinOp<'a> {
match lhs {
MigratedBinOp::Parens(_)
| MigratedBinOp::StaticDispatch { .. }
| MigratedBinOp::FuncStaticDispatch { .. }
| MigratedBinOp::Expr(_) => arena.alloc(lhs),
MigratedBinOp::BinOp { .. } => arena.alloc(MigratedBinOp::Parens(arena.alloc(lhs))),
}
}
fn is_staticable<'a>(buf: &Buf<'a>, value: &Expr<'a>) -> Option<&'a str> {
match value {
Expr::Var { module_name, ident } => {
let ident = if buf.flags.snakify {
snakify_camel_ident_in_bump(buf.text.bump(), ident).into_bump_str()
} else {
ident
};
if is_static_method(module_name, ident) {
Some(ident)
} else {
None
}
}
_ => None,
}
}
fn is_static_method(module_name: &str, ident: &str) -> bool {
match module_name {
"Str" => matches!(
ident,
"concat"
| "is_empty"
| "join_with"
| "split_on"
| "repeat"
| "count_utf8_bytes"
| "to_utf8"
| "starts_with"
| "ends_with"
| "trim"
| "trim_start"
| "trim_end"
| "to_dec"
| "to_f64"
| "to_f32"
| "to_u128"
| "to_i128"
| "to_u64"
| "to_i64"
| "to_u32"
| "to_i32"
| "to_u16"
| "to_i16"
| "to_u8"
| "to_i8"
| "replace_each"
| "replace_first"
| "replace_last"
| "split_first"
| "split_last"
| "walk_utf8"
| "walk_utf8_with_index"
| "reserve"
| "release_excess_capacity"
| "with_prefix"
| "contains"
| "drop_prefix"
| "drop_suffix"
| "with_ascii_lowercased"
| "with_ascii_uppercased"
| "caseless_ascii_equals"
),
// TODO: other modules
"List" => matches!(
ident,
"is_empty"
| "get"
| "set"
| "replace"
| "update"
| "append"
| "append_if_ok"
| "prepend"
| "prepend_if_ok"
| "map"
| "len"
| "walk_backwards"
| "concat"
| "first"
| "single"
| "repeat"
| "reverse"
| "join"
| "keep_if"
| "contains"
| "sum"
| "walk"
| "last"
| "keep_oks"
| "keep_errs"
| "map_with_index"
| "map2"
| "map3"
| "product"
| "walk_with_index"
| "walk_until"
| "walk_with_index_until"
| "walk_from"
| "walk_from_until"
| "range"
| "sort_with"
| "swap"
| "drop_at"
| "min"
| "max"
| "map4"
| "map_try"
| "walk_try"
| "join_map"
| "any"
| "take_first"
| "take_last"
| "drop_first"
| "drop_last"
| "find_first"
| "find_last"
| "find_first_index"
| "find_last_index"
| "sublist"
| "intersperse"
| "split_at"
| "split_on"
| "split_on_list"
| "split_first"
| "split_last"
| "starts_with"
| "ends_with"
| "all"
| "drop_if"
| "sort_asc"
| "sort_desc"
| "reserve"
| "release_excess_capacity"
| "walk_backwards_until"
| "count_if"
| "chunks_of"
| "concat_utf8"
| "for_each!"
| "for_each_try!"
| "walk!"
| "walk_try!"
),
"Dict" => {
matches!(
ident,
"clear"
| "capacity"
| "reserve"
| "release_excess_capacity"
| "len"
| "is_empty"
| "get"
| "contains"
| "insert"
| "remove"
| "update"
| "walk"
| "walk_until"
| "keep_if"
| "drop_if"
| "to_list"
| "keys"
| "values"
| "insert_all"
| "keep_shared"
| "remove_all"
| "map"
| "join_map"
)
}
_ => false,
}
}
fn migrate_pizza<'a>(
buf: &Buf<'a>,
arena: &'a Bump,
expr_op_pairs: &[(Loc<Expr<'a>>, Loc<BinOp>)],
last_expr: Loc<Expr<'a>>,
) -> Result<MigratedBinOp<'a>, MigrateError> {
println!(
"Starting migrate_pizza with {} expr_op_pairs",
expr_op_pairs.len()
);
let mut stack: Vec<MigratedBinOp<'a>> = Vec::new();
let mut ops: Vec<BinOp> = Vec::new();
for (expr, op) in expr_op_pairs {
println!("Processing expression: {:?}", expr.value);
stack.push(MigratedBinOp::Expr(expr.value));
apply_ops(buf, arena, &mut stack, &mut ops, op.value.precedence())?;
println!("Pushing operator onto stack: {:?}", op.value);
ops.push(op.value);
}
// Push the last expression onto the stack
println!("Pushing last expression onto stack: {:?}", last_expr.value);
stack.push(MigratedBinOp::Expr(last_expr.value));
// Apply all remaining operators
println!("Applying all remaining operators (min_precedence: 0)");
apply_ops(buf, arena, &mut stack, &mut ops, 0)?;
println!("Final stack size: {}", stack.len());
// The final result should be at the top of the stack
let result = stack.pop().unwrap();
println!("Returning final result: {:?}", result);
Ok(result)
}
fn fmt_converted_ops(
buf: &mut Buf,
indent: u16,
converted: &MigratedBinOp,
suffix: Suffix,
) -> Result<(), MigrateError> {
match converted {
MigratedBinOp::BinOp { lhs, op, rhs } => {
// Format left-hand side
fmt_converted_ops(buf, indent, lhs, Suffix::None)?;
// Format operator
buf.indent(indent);
buf.spaces(1);
push_op(buf, *op);
buf.spaces(1);
// Format right-hand side
fmt_converted_ops(buf, indent, rhs, suffix)?;
}
MigratedBinOp::Expr(expr) => {
fmt_expr(buf, indent, expr, suffix)?;
}
MigratedBinOp::Parens(inner) => {
buf.push('(');
fmt_converted_ops(buf, indent, inner, Suffix::None)?;
buf.indent(indent);
buf.push(')');
fmt_suffix(buf, indent, suffix);
}
MigratedBinOp::StaticDispatch {
lhs,
before,
func,
args,
after,
} => {
// lhs.func(args)
fmt_converted_ops(buf, indent, lhs, Suffix::None)?;
if !before.is_empty() {
fmt_spaces(buf, before.iter(), indent);
}
buf.indent(indent);
buf.push('.');
buf.push_str(func);
buf.push('(');
for (i, arg) in args.iter().enumerate() {
fmt_expr(buf, indent, arg, Suffix::None)?;
if i != args.len() - 1 {
buf.indent(indent);
buf.push(',');
buf.spaces(1);
}
}
buf.indent(indent);
buf.push(')');
fmt_suffix(buf, indent, suffix);
if !after.is_empty() {
fmt_spaces(buf, after.iter(), indent);
}
}
MigratedBinOp::FuncStaticDispatch {
lhs,
before,
func,
args,
after,
} => {
// lhs.(func)(args)
fmt_converted_ops(buf, indent, lhs, Suffix::None)?;
if !before.is_empty() {
fmt_spaces(buf, before.iter(), indent);
}
buf.indent(indent);
buf.push('.');
buf.push('(');
fmt_expr(buf, indent, func, Suffix::None)?;
buf.push(')');
buf.push('(');
for (i, arg) in args.iter().enumerate() {
fmt_expr(buf, indent, arg, Suffix::None)?;
if i != args.len() - 1 {
buf.indent(indent);
buf.push(',');
buf.spaces(1);
}
}
buf.indent(indent);
buf.push(')');
fmt_suffix(buf, indent, suffix);
if !after.is_empty() {
fmt_spaces(buf, after.iter(), indent);
}
}
}
Ok(())
}
pub fn fmt_expr_top_level( pub fn fmt_expr_top_level(
buf: &mut Buf<'_>, buf: &mut Buf<'_>,
indent: u16, indent: u16,
@ -691,8 +1157,16 @@ fn fmt_collection<F: Fmt>(
} }
} }
for item in collection.iter() { for (i, item) in collection.iter().enumerate() {
item.fmt(buf, indent + 4, Suffix::Comma)?; let is_last = i == collection.len() - 1 && tail.is_none();
item.fmt(
buf,
indent + 4,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
if let Some(tail) = tail { if let Some(tail) = tail {
@ -743,12 +1217,22 @@ impl Fmt for TypeHeader<'_> {
fn fmt(&self, buf: &mut Buf, indent: u16, suffix: Suffix) -> Result<(), MigrateError> { fn fmt(&self, buf: &mut Buf, indent: u16, suffix: Suffix) -> Result<(), MigrateError> {
buf.indent(indent); buf.indent(indent);
buf.push_str(self.name.value); buf.push_str(self.name.value);
if !self.vars.is_empty() {
buf.push('('); buf.push('(');
for arg in self.vars { for (i, arg) in self.vars.iter().enumerate() {
arg.fmt(buf, indent, Suffix::Comma)?; let is_last = i == self.vars.len() - 1;
arg.fmt(
buf,
indent,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
buf.indent(indent); buf.indent(indent);
buf.push(')'); buf.push(')');
}
fmt_suffix(buf, indent, suffix); fmt_suffix(buf, indent, suffix);
Ok(()) Ok(())
} }
@ -762,8 +1246,16 @@ impl Fmt for Tag<'_> {
buf.push_str(name.value); buf.push_str(name.value);
if !args.is_empty() { if !args.is_empty() {
buf.push('('); buf.push('(');
for arg in args.iter() { for (i, arg) in args.iter().enumerate() {
arg.fmt(buf, indent, Suffix::Comma)?; let is_last = i == args.len() - 1;
arg.fmt(
buf,
indent,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
buf.indent(indent); buf.indent(indent);
buf.push(')'); buf.push(')');
@ -788,8 +1280,16 @@ impl Fmt for TypeAnnotation<'_> {
fn fmt(&self, buf: &mut Buf, indent: u16, suffix: Suffix) -> Result<(), MigrateError> { fn fmt(&self, buf: &mut Buf, indent: u16, suffix: Suffix) -> Result<(), MigrateError> {
match self { match self {
TypeAnnotation::Function(args, function_arrow, res) => { TypeAnnotation::Function(args, function_arrow, res) => {
for arg in args.iter() { for (i, arg) in args.iter().enumerate() {
arg.fmt(buf, indent, Suffix::Comma)?; let is_last = i == args.len() - 1;
arg.fmt(
buf,
indent,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
match function_arrow { match function_arrow {
FunctionArrow::Pure => { FunctionArrow::Pure => {
@ -811,8 +1311,16 @@ impl Fmt for TypeAnnotation<'_> {
buf.push_str(func); buf.push_str(func);
if !locs.is_empty() { if !locs.is_empty() {
buf.push('('); buf.push('(');
for loc in locs.iter() { for (i, loc) in locs.iter().enumerate() {
loc.fmt(buf, indent, Suffix::Comma)?; let is_last = i == locs.len() - 1;
loc.fmt(
buf,
indent,
if is_last { Suffix::None } else { Suffix::Comma },
)?;
if !is_last {
buf.spaces(1);
}
} }
buf.indent(indent); buf.indent(indent);
buf.push(')'); buf.push(')');
@ -865,8 +1373,7 @@ impl Fmt for TypeAnnotation<'_> {
buf.push_str("_"); buf.push_str("_");
} }
TypeAnnotation::Wildcard => { TypeAnnotation::Wildcard => {
buf.indent(indent); return Err(MigrateError::WildcardTypeNotSupported);
buf.push_str("*");
} }
TypeAnnotation::Where(left, clauses) => { TypeAnnotation::Where(left, clauses) => {
left.fmt(buf, indent, Suffix::None)?; left.fmt(buf, indent, Suffix::None)?;
@ -1022,6 +1529,11 @@ impl Fmt for TypeDef<'_> {
typ, typ,
derived, derived,
} => { } => {
if true {
// current new compiler doesn't support opaque types
// TODO: fix this!
return Err(MigrateError::OpaqueNotSupported);
}
header.fmt(buf, indent, Suffix::None)?; header.fmt(buf, indent, Suffix::None)?;
buf.push_str(" :="); buf.push_str(" :=");
buf.spaces(1); buf.spaces(1);
@ -1046,11 +1558,14 @@ impl Fmt for TypeDef<'_> {
#[derive(Debug)] #[derive(Debug)]
pub enum MigrateError { pub enum MigrateError {
AbilitiesNotSupported, AbilitiesNotSupported,
WildcardTypeNotSupported,
MalformedIdentNotSupported, MalformedIdentNotSupported,
MalformedPatternNotSupported, MalformedPatternNotSupported,
MalformedPatternIdentNotSupported, MalformedPatternIdentNotSupported,
MalformedPatternAsExprNotSupported, MalformedPatternAsExprNotSupported,
PrecedenceConflictNotSupported, PrecedenceConflictNotSupported,
OpaqueNotSupported,
PizzaOpRhsNotSupported,
} }
impl Fmt for ValueDef<'_> { impl Fmt for ValueDef<'_> {
@ -1133,6 +1648,7 @@ impl Fmt for IngestedFileImport<'_> {
buf.indent(indent); buf.indent(indent);
buf.push_str("import"); buf.push_str("import");
buf.spaces(1);
let indent = indent + 4; let indent = indent + 4;
@ -1236,6 +1752,7 @@ impl Fmt for ModuleImport<'_> {
buf.indent(indent); buf.indent(indent);
buf.push_str("import"); buf.push_str("import");
buf.spaces(1);
if !before_name.is_empty() { if !before_name.is_empty() {
fmt_spaces(buf, before_name.iter(), indent); fmt_spaces(buf, before_name.iter(), indent);
@ -1250,10 +1767,13 @@ impl Fmt for ModuleImport<'_> {
} }
if let Some(exposed) = exposed { if let Some(exposed) = exposed {
buf.spaces(1);
exposed.keyword.fmt(buf, indent, Suffix::None)?; exposed.keyword.fmt(buf, indent, Suffix::None)?;
buf.spaces(1);
fmt_collection(buf, indent, Braces::Square, None, exposed.item, None)?; fmt_collection(buf, indent, Braces::Square, None, exposed.item, None)?;
} }
fmt_suffix(buf, indent, suffix); fmt_suffix(buf, indent, suffix);
buf.ensure_ends_with_newline();
Ok(()) Ok(())
} }
} }
@ -1399,3 +1919,9 @@ pub fn fmt_defs(buf: &mut Buf<'_>, defs: &Defs<'_>) -> Result<(), MigrateError>
} }
Ok(()) Ok(())
} }
fn snakify_camel_ident_in_bump<'a>(arena: &'a Bump, string: &str) -> String<'a> {
let mut buf = Buf::new_in(arena, MigrationFlags::default());
snakify_camel_ident(&mut buf, string);
buf.text
}

View file

@ -216,7 +216,7 @@ impl BinOp {
ASSOCIATIVITY_TABLE[self as usize] ASSOCIATIVITY_TABLE[self as usize]
} }
fn precedence(self) -> u8 { pub fn precedence(self) -> u8 {
const PRECEDENCE_TABLE: [u8; 18] = generate_precedence_table(); const PRECEDENCE_TABLE: [u8; 18] = generate_precedence_table();
PRECEDENCE_TABLE[self as usize] PRECEDENCE_TABLE[self as usize]

View file

@ -5,6 +5,6 @@ O({p: if
A A
} else { } else {
&m &m
}}, # }} #
) : e ) : e
i i

View file

@ -1,2 +1,2 @@
N((implements),h,(0),) : B N((implements), h, (0)) : B
T T

View file

@ -1,4 +1,4 @@
B(@A,) : w B(@A) : w
# #
@A = e @A = e
i i

View file

@ -1,3 +1,3 @@
Zx((e # Zx((e #
),f,) : i ), f) : i
s s

View file

@ -1,2 +1,2 @@
U((b(a,)),) : b U((b(a))) : b
a a

View file

@ -1,4 +1,4 @@
M({s: s({J& M({s: s({J&
},)},{s: s({J& })}, {s: s({J&
},)},) : p })}) : p
y y

View file

@ -1,4 +1,4 @@
Q(( Q((
""" """
"""("",)),) : a """(""))) : a
q q

View file

@ -1,6 +1,6 @@
il3(|k| # w#z il3(|k| # w#z
{ {
CCC(@C,( # i CCC(@C, ( # i
t!(K,)),) : i t!(K))) : i
C C
},) })

View file

@ -1,4 +1,4 @@
O() : O(z, O : O(z
# #
) )
b # b #

View file

@ -1,4 +1,4 @@
foo: [True,Perhaps(Thing,),] foo: [True, Perhaps(Thing)]
foo = True foo = True
42 42

View file

@ -1,5 +1,5 @@
launchTheNukes: {}, => Result(Bool,LaunchNukeErr,) launchTheNukes: {} => Result(Bool, LaunchNukeErr)
launchTheNukes = |{}| launchTheNukes = |{}|
crash("todo",) crash("todo")
launchTheNukes launchTheNukes

View file

@ -1,5 +1,5 @@
x: a x: a
where where
e e
implements K, -> Z implements K -> Z
s s

View file

@ -1,4 +1,4 @@
H() : p H : p
# #
s = p s = p
d # d #

View file

@ -1,4 +1,4 @@
J() : [ J : [
] where e ] where e
implements T implements T
i i

View file

@ -1,3 +1,3 @@
1: (f, 1: (f,
ww, -> p,..e) ww -> p, ..e)
Mh Mh

View file

@ -1,4 +1,4 @@
{x,y,}: Foo {x, y}: Foo
{x,y,} = {x: "foo"y: 3.14} {x, y} = {x: "foo", y: 3.14}
x x

View file

@ -1,4 +1,4 @@
UserId(x,): [UserId(I64,),] UserId(x): [UserId(I64)]
UserId(x,) = UserId(42,) UserId(x) = UserId(42)
x x

View file

@ -1,4 +1,4 @@
(x,y,): Foo (x, y): Foo
(x,y,) = ("foo",3.14,) (x, y) = ("foo", 3.14)
x x

View file

@ -1,4 +1,4 @@
A( A(
e, # g e # g
) : A ) : A
AA AA

View file

@ -1,2 +1,2 @@
s: eas A()as A() s: eas Aas A
s s

View file

@ -1,3 +1,3 @@
3: (..n # 3: (..n #
), -> n ) -> n
0 0

View file

@ -1,3 +1,3 @@
Str.getUnsafe(haystack,haystackIndex,) Str.getUnsafe(haystack, haystackIndex)
== ==
Str.getUnsafe(needle,needleIndex,) Str.getUnsafe(needle, needleIndex)

View file

@ -1,2 +1,2 @@
foo foo
|> Dict.keepIf(|(k,_v,)| List.contains(keysToDelete,k,) |> Bool.not,) .(Dict.keepIf)(|(k, _v)| List.contains(keysToDelete, k).(Bool.not)())

View file

@ -1,3 +1,3 @@
a: N({h a: N({h
},) })
g g

View file

@ -1,2 +1,2 @@
0({lxtl: (se # 0({l xt l: (se #
)},) )})

View file

@ -1 +1 @@
Whee(12,34,) Whee(12, 34)

View file

@ -1 +1 @@
!whee(12,foo,) !whee(12, foo)

View file

@ -1,2 +1,2 @@
|{x,y,} as point@Location(inner,) as outer| |{x, y} as point@Location(inner) as outer|
crash("",) crash("")

View file

@ -1,3 +1,3 @@
e: J e: J
as H(), -> A as H -> A
r r

View file

@ -1,3 +1,3 @@
d + d +
(|w| x)( (|w| x)(
x,) x)

View file

@ -1,5 +1,5 @@
t = t =
""" """
" "
"""("",) """("")
S S

View file

@ -1 +1 @@
launchTheNukes!(123,) launchTheNukes!(123)

View file

@ -1,3 +1,4 @@
importP{_: h import P{_: h
} }
t! t!

View file

@ -1,4 +1,4 @@
|I({p? Y( |I({p? Y(
Y,)?,},[ Y)?},[
],)| ])|
K # ( K # (

View file

@ -1,2 +1,2 @@
m0(|w| w?(e,),) m0(|w| w?(e))
/ s / s

View file

@ -1,3 +1,3 @@
m m
^ (-|w| m)( ^ (-|w| m)(
w,) w)

View file

@ -1,8 +1,8 @@
Model(position,) : Model(position) :
{evaluated: Set(position,) {evaluated: Set(position),
openSet: Set(position,) openSet: Set(position),
costs: Dict.Dict(position,F64,) costs: Dict.Dict(position, F64),
cameFrom: Dict.Dict(position,position,) cameFrom: Dict.Dict(position, position)
} }
a a

View file

@ -1,9 +1,9 @@
PP(P(@P(P( PP(P(@P(P(
P(PPP(P( P(PPP(P(
PPP,), PPP)
), )
), ),
PP(mport, # <|"P PP(mport # <|"P
),), ))
),), ))
) )

View file

@ -1,3 +1,3 @@
1: (M,b, # , 1: (M, b, # ,
h, -> g,..e) h -> g, ..e)
h h

View file

@ -1,2 +1,2 @@
3 # test! 3 # test!
+ 4 + 4

View file

@ -1,4 +1,4 @@
1(0, # 1(0 #
# #
): gi ): gi
M M

View file

@ -1,2 +1,2 @@
3 # 2 × 2 3 # 2 × 2
+ 4 + 4

View file

@ -1,4 +1,4 @@
x > x >
x({ x({
},) < r }) < r

View file

@ -1,11 +1,11 @@
_ = crash("",) _ = crash("")
_ = crash("","",) _ = crash("", "")
_ = crash(15,123,) _ = crash(15, 123)
_ = try(foo,(|_| crash("",)),) _ = try(foo, (|_| crash("")))
_ = _ =
{ {
_ = crash("",) _ = crash("")
crash crash
} }
{f: crash("",)} {f: crash("")}

View file

@ -1,4 +1,4 @@
1(0( # 1(0( #
0,), 0)
)(f,): f )(f): f
t t

View file

@ -1,6 +1,6 @@
1(ts(0, 1(ts(0
# #
), )
)(f,): i7f )(f): i7f
e e

View file

@ -3,4 +3,4 @@
- -
""" """
"""()? """()?
}))(Y,) }))(Y)

View file

@ -1,4 +1,4 @@
0( 0(
# #
f,): f f): f
t t

View file

@ -1,2 +1,2 @@
1: w, -> w, -> p 1: w -> w -> p
h h

View file

@ -1 +1 @@
dbg(dbg,g,g,) dbg(dbg, g, g)

View file

@ -1,2 +1,2 @@
dbg(dbg, dbg(dbg,
a,g,) a, g)

View file

@ -1,2 +1,2 @@
dbg(d,z,) dbg(d, z)
dd dd

View file

@ -1,4 +1,4 @@
dbg( dbg(
izzb, izzb,
interfacesb,) interfacesb)

View file

@ -1,2 +1,2 @@
dbg(a / a,) dbg(a / a)
d d

View file

@ -1,4 +1,4 @@
({ ({
(dbg(r,)) (dbg(r))
r r
}) })

View file

@ -1,4 +1,4 @@
dbg (5, dbg (5,
666,) 666)
4 4

View file

@ -1,6 +1,6 @@
dbg dbg
(q( (q(
qt,)) qt))
g( g(
qt,) qt)

View file

@ -1,4 +1,4 @@
launchTheNukes! = |{}| launchTheNukes! = |{}|
boom boom
launchTheNukes!({},) launchTheNukes!({})

View file

@ -1,4 +1,4 @@
e = e =
""" """
"""(a,) """(a)
p p

View file

@ -1,7 +1,7 @@
main = main =
{ {
a = "Foo" a = "Foo"
Stdout.line?(a,) Stdout.line?(a)
printBar? printBar?
} }
@ -9,5 +9,5 @@ main =
printBar = printBar =
{ {
b = "Bar" b = "Bar"
Stdout.line(b,) Stdout.line(b)
} }

Some files were not shown because too many files have changed in this diff Show more