Skip to content

Commit b622b65

Browse files
committed
Add Parenthesized flag to Lambda AST node to fix multi-param lambda merging
When parsing multi-param lambdas like `acc,x -> body` where parameters are separated by commas without parentheses, the parser was incorrectly merging preceding identifiers with explicitly parenthesized lambdas like `(x -> y)`. This fix: - Adds `Parenthesized bool` field to ast.Lambda - Sets this flag when a lambda is wrapped in explicit parentheses - Checks the flag in mergeMultiParamLambdas to skip parenthesized lambdas This correctly parses: - `arrayFold(acc,x -> body, arr, init)` - merges acc into lambda params - `delay(time, (time -> 0.5), ...)` - keeps time as separate argument Fixes all 17 statements in 02718_array_fold test and 3 in 02418_aggregate_combinators.
1 parent 9239c04 commit b622b65

4 files changed

Lines changed: 65 additions & 31 deletions

File tree

ast/ast.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1440,9 +1440,10 @@ func (t *TupleAccess) expressionNode() {}
14401440

14411441
// Lambda represents a lambda expression.
14421442
type Lambda struct {
1443-
Position token.Position `json:"-"`
1444-
Parameters []string `json:"parameters"`
1445-
Body Expression `json:"body"`
1443+
Position token.Position `json:"-"`
1444+
Parameters []string `json:"parameters"`
1445+
Body Expression `json:"body"`
1446+
Parenthesized bool `json:"-"` // True if wrapped in explicit parentheses
14461447
}
14471448

14481449
func (l *Lambda) Pos() token.Position { return l.Position }

parser/expression.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,58 @@ func (p *Parser) parseFunctionArgumentList() []ast.Expression {
158158
}
159159
}
160160

161+
// Post-process: merge consecutive identifiers followed by a lambda into a multi-param lambda
162+
// Pattern: [Ident("acc"), Lambda(["x"], body)] -> [Lambda(["acc", "x"], body)]
163+
exprs = mergeMultiParamLambdas(exprs)
164+
165+
return exprs
166+
}
167+
168+
// mergeMultiParamLambdas looks for pattern [Ident, Ident, ..., Lambda] at the START
169+
// of the expression list and merges them into a single multi-param lambda.
170+
// This handles ClickHouse's syntax: acc,x -> body (multi-param lambda without parentheses)
171+
// This ONLY applies at position 0 - identifiers in the middle are regular arguments.
172+
func mergeMultiParamLambdas(exprs []ast.Expression) []ast.Expression {
173+
if len(exprs) < 2 {
174+
return exprs
175+
}
176+
177+
// Only check at position 0 - the pattern must start at the beginning
178+
if ident, ok := exprs[0].(*ast.Identifier); ok && len(ident.Parts) == 1 {
179+
// Count consecutive simple identifiers at the start
180+
j := 0
181+
var params []string
182+
for j < len(exprs) {
183+
if id, ok := exprs[j].(*ast.Identifier); ok && len(id.Parts) == 1 {
184+
params = append(params, id.Name())
185+
j++
186+
} else {
187+
break
188+
}
189+
}
190+
// Check if the next expression is a lambda and we have at least one identifier
191+
if j < len(exprs) && len(params) >= 1 {
192+
if lambda, ok := exprs[j].(*ast.Lambda); ok {
193+
// Don't merge if lambda was explicitly parenthesized
194+
// e.g., f(a, (x -> y)) should NOT merge 'a' into the lambda
195+
if lambda.Parenthesized {
196+
return exprs
197+
}
198+
// Merge the identifiers into the lambda's parameters
199+
newParams := make([]string, 0, len(params)+len(lambda.Parameters))
200+
newParams = append(newParams, params...)
201+
newParams = append(newParams, lambda.Parameters...)
202+
lambda.Parameters = newParams
203+
// Return lambda followed by remaining expressions
204+
result := make([]ast.Expression, 0, len(exprs)-j)
205+
result = append(result, lambda)
206+
result = append(result, exprs[j+1:]...)
207+
return result
208+
}
209+
}
210+
}
211+
212+
// No merge needed
161213
return exprs
162214
}
163215

@@ -1019,6 +1071,13 @@ func (p *Parser) parseGroupedOrTuple() ast.Expression {
10191071
binExpr.Parenthesized = true
10201072
}
10211073

1074+
// Mark lambda expressions as parenthesized so we don't merge them
1075+
// with preceding identifiers in multi-param lambda detection
1076+
// e.g., f(a, (x -> y)) should NOT merge 'a' into the lambda
1077+
if lambda, ok := first.(*ast.Lambda); ok {
1078+
lambda.Parenthesized = true
1079+
}
1080+
10221081
return first
10231082
}
10241083

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt11": true,
4-
"stmt16": true,
5-
"stmt28": true
6-
}
7-
}
1+
{}
Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt10": true,
4-
"stmt12": true,
5-
"stmt13": true,
6-
"stmt14": true,
7-
"stmt15": true,
8-
"stmt16": true,
9-
"stmt17": true,
10-
"stmt18": true,
11-
"stmt20": true,
12-
"stmt21": true,
13-
"stmt22": true,
14-
"stmt27": true,
15-
"stmt34": true,
16-
"stmt6": true,
17-
"stmt7": true,
18-
"stmt8": true,
19-
"stmt9": true
20-
}
21-
}
1+
{}

0 commit comments

Comments
 (0)