Skip to content

fix: fold exprs within tuple_map evaluation#5877

Merged
max-sixty merged 3 commits into
PRQL:mainfrom
bioteam:kg/tuple-map-fold-exprs
May 12, 2026
Merged

fix: fold exprs within tuple_map evaluation#5877
max-sixty merged 3 commits into
PRQL:mainfrom
bioteam:kg/tuple-map-fold-exprs

Conversation

@kgutwin

@kgutwin kgutwin commented May 11, 2026

Copy link
Copy Markdown
Collaborator

The following PRQL fails with a panic during compilation:

let add_four = func a -> a + 4

from foo
select {x, y}
derive (tuple_map add_four foo.*)

The error is:

The application panicked (crashed).
Message:  called `Option::unwrap()` on a `None` value
Location: prqlc/prqlc/src/semantic/resolver/transforms.rs:978

The ultimate cause is that the invocation of tuple_map does not directly produce a tuple of evaluated results, but rather produces a tuple of ExprKind::FuncCall instances. Those FuncCall instances are not expected at time of lineage calculation (where the panic happens in transforms.rs).

The only place where tuple_map is currently used is in std.prql, for example:

let intersect = `default_db.bottom`<relation> top<relation> -> <relation> (
t = top
join (b = bottom) (tuple_every (tuple_map _eq (tuple_zip t.* b.*)))
select t.*
)

In the above case, tuple_map is being wrapped in tuple_every, which effectively triggers a fold of the FuncCall instances that it returns (somehow... I'm not sure how, exactly). However, when tuple_map is used on its own, those FuncCall instances are not evaluated as expected.

This PR updates the implementation of tuple_map so that the FuncCall instances generated through the map process are folded immediately after they are created, ensuring that they are not propagated through to lineage calculation.

@prql-bot prql-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix correctly addresses the panic by folding each mapped FuncCall eagerly, and the test exercises the failure case from the description.

One suggestion on the error handling. The match with Err(_) => expr fallback silently discards real resolver errors (e.g. arity mismatch when the mapped function takes more than one arg) and falls back to the original unfolded FuncCall — which is exactly the shape that triggers the lineage-calculation panic this PR aims to prevent. So in the error path the user loses the diagnostic and may still hit the panic.

Every other fold_expr call in this file propagates with ?. Doing the same here keeps the behavior consistent and surfaces legitimate errors to the user. I verified the suggested form compiles and that test_tuple_map plus the std.prql join/tuple_every(tuple_map …) paths still produce identical SQL.

Comment thread prqlc/prqlc/src/semantic/resolver/transforms.rs Outdated
@max-sixty max-sixty merged commit dec8c6c into PRQL:main May 12, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants