fix: quote names in fmt when they need backticks - #6200
Conversation
prql-bot
left a comment
There was a problem hiding this comment.
Self-review. The six declaration sites are right and the round-trip test covers each of them, but the same class of bug survives one site further down the same file: named arguments in a function call are still emitted verbatim.
prqlc/prqlc/src/codegen/ast.rs, in the FuncCall arm — for (name, arg) in &func_call.named_args { … r += opt.consume(name)?; … } — writes name raw, while the Func arm just below it already routes param.name and the named_params through write_ident_part. ident_part() in the parser accepts any Ident token, so a backtick-quoted named arg parses fine, and this is worse than the declaration case because the mangled output still parses — as a different call:
$ printf 'from t\nderive x = (foo `my arg`:5)\n' | prqlc fmt -
from t
derive x = (foo my arg:5)
$ printf 'from t\nderive x = (foo my arg:5)\n' | prqlc fmt -
from t
derive x = (foo arg:5 my)my silently becomes a positional argument. No error, no diagnostic — fmt just rewrites the query's meaning. Since this is the same concern the PR is fixing and it's four lines from the hunks, I'll fold it in rather than open a seventh open bot PR, and update the title/body to match.
The test-rust (x86_64-unknown-linux-gnu, …) failure is unrelated infrastructure: it failed in the Wait for database step with Error: Timed out waiting for: tcp:1433 — the dbs-mssql-1 container didn't accept connections inside the action's 60s timeout. Nothing in the diff touches DB tests. No open issue tracks this wait-on timeout, and it's a single occurrence so far; the push below re-runs the matrix.
Verification notes
Ran against the merged tree (refs/pull/6200/merge, head dee8270):
-
cargo test -p prqlc --lib— 81 passed, 1 ignored.cargo test -p prqlc --test integration— 489 passed, 5 ignored. So theTyTupleFieldchange incodegen/types.rsdoesn't churn anydebug_lineageor error-message snapshot, which was the risk of touchingwrite_ty's output path. -
Confirmed the fix does what the PR body claims — this now round-trips instead of emitting
let my var = …:let `my var` = (from t | select {x}) -
Checked the other raw-name emission sites in
codegen/.Ty::name(incodegen/types.rs,if let Some(name) = &self.name { Some(name.clone()) }) is also unquoted, but it's only ever set during semantic resolution (semantic/resolver/stmt.rs,ty.name = Some(ident.name.clone())) —fmtruns on the parser AST where it'sNone, so it's not afmtcorrectness gap.Internal(operator_name)and theQueryDefotherkeys aren't user idents. Everything else already goes throughwrite_ident_partorIdent::write. -
Spot-checked the PR body's claim about enum variants:
TyKind::Enumholds aBox<Expr>, so variants really are expression aliases and were already quoted. A backtick-quoted variant name round-trips unchanged:enum e {`Paid Up` = 0}
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.
fmt when they need backticksfmt when they need backticks
prqlc fmtemits declaration and named-argument names verbatim, so any name that needs backticks — one containing a space, or one that collides with a keyword — loses its quoting and the formatted output no longer means what the source did. The fix routes those names through the existingwrite_ident_parthelper, which is already used for aliases, function params, and idents.Found by the nightly survey while reviewing
prqlc/prqlc/src/codegen/ast.rs; the named-argument case was found by the review pass on this PR.Before,
fmtturned a working query into a broken one:Named arguments fail more quietly, because the mangled output still parses — as a different call, with the first half of the name becoming a positional argument:
Affected sites, all now quoted:
let(both the typed/valueless branch and the plain branch),into,type,enum,module, type tuple field names (type t = {`my field` = int}), and function-call named arguments. Enum variant names were already correct — they parse as expression aliases, which went throughwrite_ident_partalready.Verification
Regression tests are
codegen::ast::test::test_quoted_declaration_names, covering all six declaration sites plus a keyword-named declaration, andcodegen::ast::test::test_quoted_named_arg. Reverting just the source hunks while keeping the tests makes them fail:assert_is_formattedround-trips through the parser, so each case also asserts the output re-parses to the same source.Local runs:
cargo test -p prqlc-parser -p prqlc -p prqlc-macros -p compile-filesand-p mdbook-prqlall pass;cargo fmt --checkandcargo clippy --all-targetsare clean.task prqlc:pull-requestcould not run here —cargo-instaandcargo-nextestaren't on the sandbox PATH (the subject of #6144) — so the equivalent package set was run with plaincargo test.