fix: return an error instead of panicking on an empty ident path - #6223
Conversation
prql-bot
left a comment
There was a problem hiding this comment.
One thing missing: this is a user-facing change — json::to_pl / json::to_rq and the Python and JS bindings go from a panic to an error on malformed input, and Ident::try_from_path is new public API — so it needs a CHANGELOG.md line under Fixes per development.md ("Contribution workflow → Commits"). #6221 is the same class of fix and carries one. Pushing the entry to this branch rather than leaving it for a maintainer.
Nothing else. Reviewed as a self-authored PR, so no approval — a maintainer's call.
What was checked
The interesting question is whether from_path's new .expect is a genuine compiler-bug invariant (CLAUDE.md allows .expect only for those) rather than a relocated user-input panic. Walked all of its remaining call sites; each builds a path that is non-empty by construction:
Ident::prepend—parts.extend(self), and anIdentalways yields at least itsnameparser/expr.rsident()—parts.push(last)immediately before the callsemantic/module.rsfind_main— one arm guarded by!path.is_empty(), the other pushesNS_MAINsemantic/resolver/stmt.rs—self.current_module_path.push(ident.name)immediately beforesemantic/lowering.rs— the relative path is guarded byrelative_to_database.is_empty();database_module_pathdefaults to[NS_DEFAULT_DB]insemantic/mod.rs- the rest are static literals (
vec!["std", "select"]and similar)
Also checked for sibling panics on the same deserialization path: Span is the only other hand-written Deserialize in the workspace, and it already returns de::Error::custom rather than panicking, so Ident was the outlier.
cargo test -p prqlc-parser --lib — 101 passed, 0 failed, including the two new tests. That covers the parser crate only; the tests matrix is green on 13fcfa38 for the rest.
Ident'sDeserializeimpl fed the deserializedVec<String>straight intoIdent::from_path, which doespath.pop().unwrap()— so an ident serialized as an empty array panicked instead of erroring. That path is reachable from user input via the publicjson::to_pl/json::to_rqentry points, and from there through the Python (pl_to_prql,pl_to_rq,rq_to_sql) and JS bindings, which contradicts CLAUDE.md's "never panic on user input".This adds
Ident::try_from_path, returningNoneon an empty path, and hasDeserializemap that toserde::de::Error::invalid_length.from_pathkeeps its panicking signature for the ~25 internal call sites that build idents from static paths, now via.expect— a compiler-bug invariant rather than a user-input path.Verified on the end-to-end public API:
json::to_plon a document containing{"Ident":[]}panicked atident.rs:26before, and now returnsError: invalid length 0, expected an ident with at least one part at line 1 column 118.Verification
Regression test
deserialize_empty_pathinprqlc/prqlc-parser/src/parser/pr/ident.rsfails on the pre-fix code with the panic above.task prqlc:pull-requestcould not run in the tend sandbox —cargo instais not on PATH there, which is what #6144 addresses. The plaincargo testruns above cover the same two crates.