fix: unique wrapper param names for nested currying#5979
Conversation
When a function returns a partially-applied function, the resolver materializes a wrapper whose synthesized params were named by a per-materialization index (_partial_0, _partial_1, ...). Nested partial applications share the NS_PARAM namespace, so these names collided across levels and a reference resolved to the wrong binding — currying through more than two let-bound wrappers produced the wrong result. Name the wrapper params with the resolver's global id generator so they are unique across materializations. Closes #5978 Co-Authored-By: Claude <noreply@anthropic.com>
|
@tantmd lmk if you could have a look, thank you! |
|
Thanks @tantmd — that's a fair distinction, and you're right that this PR doesn't change the underlying design. To be explicit about the scope: this is a minimal regression fix. The synthesized wrapper params still live in the shared A proper fix would isolate the wrapper params from user scope — e.g. a dedicated namespace for synthesized partials, or threading them through @max-sixty — happy to either land this as the targeted fix and open a follow-up issue tracking the namespace-isolation work, or to dig into the deeper refactor if you'd prefer that here. Your call. |

Problem
Currying a function through more than two
let-bound wrappers produced the wrong result. From #5978:yandzshould be equivalent, butycompiled to100 + 20 + 20instead of100 + 20 + 3—cpicked up the value ofb.Root cause: when a function returns a partially-applied function,
materialize_functionsynthesizes a wrapper whose params were named by a per-materialization index (_partial_0,_partial_1, …). Nested partial applications share theNS_PARAMnamespace, so these names collided across levels and a reference resolved to the wrong binding. (The reporter's analysis pinpointed this exactly, including why the 4-param case happened to work — the colliding param landed on_partial_1there, not_partial_0.)Solution
Name the wrapper params with the resolver's global
idgenerator (self.id.gen()) so they are unique across materializations and can't collide in the shared namespace. This mirrors howextract_partial_applicationalready derives its_partial_names from a uniquearg.id.Testing
Added
test_nested_curryinginintegration/sql.rs, which fails before the fix (100 + 20 + 20 AS y) and passes after. Verified both the 3-param and 4-param reproductions from the issue now compile correctly, andtask prqlc:pull-requestpasses with no regressions.Closes #5978 — automated triage