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.

Solutions

  1. Make the predicate genuinely depend on the iteration variable, e.g. tag.exists(t, t == "x") instead of tag.exists(t, true)
  2. 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

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


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/3494a7b823087875. Report an issue: GitHub.

Appendix: 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)