From dee8270a81235ec38da3c769bab6f7417a2260a7 Mon Sep 17 00:00:00 2001 From: prql-bot <107324867+prql-bot@users.noreply.github.com> Date: Mon, 17 Aug 2026 06:47:41 +0000 Subject: [PATCH 1/2] fix: quote declaration names in `fmt` when they need backticks --- prqlc/prqlc/src/codegen/ast.rs | 70 +++++++++++++++++++++++++++++--- prqlc/prqlc/src/codegen/types.rs | 3 +- 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/prqlc/prqlc/src/codegen/ast.rs b/prqlc/prqlc/src/codegen/ast.rs index 6d0d44ff5a4c..f95dbb090771 100644 --- a/prqlc/prqlc/src/codegen/ast.rs +++ b/prqlc/prqlc/src/codegen/ast.rs @@ -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,63 @@ 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 +"#, + ); + } + #[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 { From f77588c7d769bd0ab5409ba1bd497996f83ee62a Mon Sep 17 00:00:00 2001 From: prql-bot <107324867+prql-bot@users.noreply.github.com> Date: Mon, 17 Aug 2026 06:59:18 +0000 Subject: [PATCH 2/2] fix: quote named-argument names in `fmt` too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A backtick-quoted named argument in a function call was emitted verbatim, so `derive x = (foo `my arg`:5)` formatted to `foo my arg:5` — which still parses, but as a different call, with `my` becoming a positional argument. --- prqlc/prqlc/src/codegen/ast.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/prqlc/prqlc/src/codegen/ast.rs b/prqlc/prqlc/src/codegen/ast.rs index f95dbb090771..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(":")?; @@ -784,6 +784,19 @@ 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(