diff --git a/prqlc/prqlc/src/codegen/ast.rs b/prqlc/prqlc/src/codegen/ast.rs index 6d0d44ff5a4c..8f8daa83d1bd 100644 --- a/prqlc/prqlc/src/codegen/ast.rs +++ b/prqlc/prqlc/src/codegen/ast.rs @@ -162,7 +162,7 @@ impl WriteSource for pr::ExprKind { for (name, arg) in &func_call.named_args { r += opt.consume(" ")?; - r += opt.consume(name)?; + r += opt.consume(&write_ident_part(name))?; r += opt.consume(":")?; @@ -402,7 +402,8 @@ impl WriteSource for pr::Stmt { "".to_string() }; - r += opt.consume(&format!("let {} {}", var_def.name, typo))?; + r += + opt.consume(&format!("let {} {}", write_ident_part(&var_def.name), typo))?; if let Some(val) = &var_def.value { r += opt.consume("= ")?; @@ -412,7 +413,7 @@ impl WriteSource for pr::Stmt { } pr::VarDefKind::Let => { - r += opt.consume(&format!("let {} = ", var_def.name))?; + r += opt.consume(&format!("let {} = ", write_ident_part(&var_def.name)))?; r += &var_def.value.as_ref().unwrap().write(opt)?; r += "\n"; @@ -433,7 +434,7 @@ impl WriteSource for pr::Stmt { } if var_def.kind == pr::VarDefKind::Into { - r += &format!("into {}", var_def.name); + r += &format!("into {}", write_ident_part(&var_def.name)); r += "\n"; } } @@ -446,18 +447,18 @@ impl WriteSource for pr::Stmt { .. }, }) => { - r += opt.consume(&format!("enum {} ", name))?; + r += opt.consume(&format!("enum {} ", write_ident_part(name)))?; r += &enum_tuple.write(opt)?; r += "\n"; } pr::StmtKind::TypeDef(type_def) => { - r += opt.consume(&format!("type {}", type_def.name))?; + r += opt.consume(&format!("type {}", write_ident_part(&type_def.name)))?; r += opt.consume(" = ")?; r += &type_def.value.kind.write(opt)?; r += "\n"; } pr::StmtKind::ModuleDef(module_def) => { - r += &format!("module {} {{\n", module_def.name); + r += &format!("module {} {{\n", write_ident_part(&module_def.name)); opt.indent += 1; r += &module_def.stmts.write(opt.clone())?; @@ -726,6 +727,76 @@ into a ); } + /// Declaration names that aren't valid bare idents must keep their + /// backticks, otherwise `fmt` emits source that no longer parses. + #[test] + fn test_quoted_declaration_names() { + assert_is_formatted( + r#" +let `my var` = 5 +"#, + ); + + assert_is_formatted( + r#" +let `my var` +"#, + ); + + assert_is_formatted( + r#" +5 +into `my var` +"#, + ); + + assert_is_formatted( + r#" +type `my type` = int +"#, + ); + + assert_is_formatted( + r#" +type t = {`my field` = int} +"#, + ); + + assert_is_formatted( + r#" +enum `my enum` {Paid = 0} +"#, + ); + + assert_is_formatted( + r#" +module `my mod` { + let a = 5 +} +"#, + ); + + // A declaration named after a keyword also needs quoting + assert_is_formatted( + r#" +let `case` = 5 +"#, + ); + } + + /// Named arguments need their backticks too. Unlike the declaration names + /// above, dropping them produces output that still parses — as a different + /// call, since the unquoted name splits into a positional argument. + #[test] + fn test_quoted_named_arg() { + assert_is_formatted( + r#" +from t +derive x = (foo `my arg`:5) +"#, + ); + } + #[test] fn test_query_def() { assert_is_formatted( diff --git a/prqlc/prqlc/src/codegen/types.rs b/prqlc/prqlc/src/codegen/types.rs index 5d035a3d2a33..72f5a7c7e9d3 100644 --- a/prqlc/prqlc/src/codegen/types.rs +++ b/prqlc/prqlc/src/codegen/types.rs @@ -1,5 +1,6 @@ use prqlc_parser::parser::pr; +use crate::codegen::ast::write_ident_part; use crate::codegen::SeparatedExprs; use super::{WriteOpt, WriteSource}; @@ -74,7 +75,7 @@ impl WriteSource for pr::TyTupleField { let mut r = String::new(); if let Some(name) = name { - r += name; + r += &write_ident_part(name); r += " = "; } if let Some(expr) = expr {