usememos/memos · error
comprehension loop step must be a call expression
Error message
comprehension loop step must be a call expression
What it means
extractPredicate expects the comprehension's LoopStep to be a call expression (accu || predicate for exists, accu && predicate for all, predicate ? accu+1 : accu for exists_one). If LoopStep is an ident, constant, or any other expr kind, the predicate cannot be extracted and this error is returned.
Source
Thrown at internal/filter/parser.go:881
// exists_one() starts at int(0) and increments via a conditional (predicate ?
// accu + 1 : accu) in the loop step.
if _, isInt := accuInit.GetConstantKind().(*exprv1.Constant_Int64Value); isInt {
if step := comp.LoopStep.GetCallExpr(); step != nil && step.Function == "_?_:_" {
return ComprehensionExistsOne, nil
}
}
return "", errors.New("unsupported comprehension type (supported: exists, all, exists_one)")
}
// extractPredicate extracts the predicate expression from the comprehension loop step.
func extractPredicate(comp *exprv1.Expr_Comprehension, _ Schema) (PredicateExpr, error) {
// The loop step is: @result || predicate(t) for exists
// or: @result && predicate(t) for all
step := comp.LoopStep.GetCallExpr()
if step == nil {
return nil, errors.New("comprehension loop step must be a call expression")
}
// exists/all: accu || predicate / accu && predicate -> predicate is arg[1].
// exists_one: predicate ? accu + 1 : accu -> predicate is arg[0].
var predicateExpr *exprv1.Expr
if step.Function == "_?_:_" {
if len(step.Args) != 3 {
return nil, errors.New("exists_one loop step must have three arguments")
}
predicateExpr = step.Args[0]
} else {
if len(step.Args) != 2 {
return nil, errors.New("comprehension loop step must have two arguments")
}
predicateExpr = step.Args[1]
}
predicateCall := predicateExpr.GetCallExpr()
if predicateCall == nil {View on GitHub (pinned to 14d757ce1f)
Solutions
- Make the predicate genuinely depend on the iteration variable, e.g. tag.exists(t, t == "x") instead of tag.exists(t, true)
- Avoid constructing comprehension ASTs by hand; use the standard macro call form
Example fix
// before `tag.exists(t, true)` // after `tag.exists(t, t == "x")`
Defensive patterns
Strategy: try-catch
Try / catch
cond, err := filter.ParseCEL(expr)
if err != nil {
if strings.Contains(err.Error(), "loop step must be a call expression") {
return fmt.Errorf("filter predicate too simple or non-standard: %w", err)
}
return err
} Prevention
- Always make comprehension predicates depend on the iteration variable
- Avoid trivial predicates like exists(t, true) which constant-fold
- Prefer textual CEL over programmatically assembled ASTs
When it happens
Trigger: A comprehension whose loop step was constant-folded or hand-built as a non-call node, e.g. exists() over a predicate that CEL constant-folded away entirely (tag.exists(t, true) may fold to a const), or an AST assembled programmatically with LoopStep left as an ident placeholder.
Common situations: Constant-folded predicates that are always true/false; custom macro expansion; cel-go version differences that fold trivial loop steps at parse time.
Related errors
- comprehension accumulator must be initialized with a constan
- unsupported comprehension type (supported: exists, all, exis
- exists_one loop step must have three arguments
- comprehension loop step must have two arguments
- comprehension predicate must be a function call
AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15).
Data as JSON: /api/errors/3494a7b823087875.
Report an issue: GitHub.