{"record":{"id":"3494a7b823087875","repo":"usememos/memos","slug":"comprehension-loop-step-must-be-a-call-expression","errorCode":null,"errorMessage":"comprehension loop step must be a call expression","messagePattern":"comprehension loop step must be a call expression","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":881,"sourceCode":"\n\t// exists_one() starts at int(0) and increments via a conditional (predicate ?\n\t// accu + 1 : accu) in the loop step.\n\tif _, isInt := accuInit.GetConstantKind().(*exprv1.Constant_Int64Value); isInt {\n\t\tif step := comp.LoopStep.GetCallExpr(); step != nil && step.Function == \"_?_:_\" {\n\t\t\treturn ComprehensionExistsOne, nil\n\t\t}\n\t}\n\n\treturn \"\", errors.New(\"unsupported comprehension type (supported: exists, all, exists_one)\")\n}\n\n// extractPredicate extracts the predicate expression from the comprehension loop step.\nfunc extractPredicate(comp *exprv1.Expr_Comprehension, _ Schema) (PredicateExpr, error) {\n\t// The loop step is: @result || predicate(t) for exists\n\t//                or: @result && predicate(t) for all\n\tstep := comp.LoopStep.GetCallExpr()\n\tif step == nil {\n\t\treturn nil, errors.New(\"comprehension loop step must be a call expression\")\n\t}\n\n\t// exists/all: accu || predicate  /  accu && predicate  -> predicate is arg[1].\n\t// exists_one: predicate ? accu + 1 : accu               -> predicate is arg[0].\n\tvar predicateExpr *exprv1.Expr\n\tif step.Function == \"_?_:_\" {\n\t\tif len(step.Args) != 3 {\n\t\t\treturn nil, errors.New(\"exists_one loop step must have three arguments\")\n\t\t}\n\t\tpredicateExpr = step.Args[0]\n\t} else {\n\t\tif len(step.Args) != 2 {\n\t\t\treturn nil, errors.New(\"comprehension loop step must have two arguments\")\n\t\t}\n\t\tpredicateExpr = step.Args[1]\n\t}\n\tpredicateCall := predicateExpr.GetCallExpr()\n\tif predicateCall == nil {","sourceCodeStart":863,"sourceCodeEnd":899,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L863-L899","documentation":"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.","triggerScenarios":"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.","commonSituations":"Constant-folded predicates that are always true/false; custom macro expansion; cel-go version differences that fold trivial loop steps at parse time.","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"],"exampleFix":"// before\n`tag.exists(t, true)`\n\n// after\n`tag.exists(t, t == \"x\")`","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"cond, err := filter.ParseCEL(expr)\nif err != nil {\n    if strings.Contains(err.Error(), \"loop step must be a call expression\") {\n        return fmt.Errorf(\"filter predicate too simple or non-standard: %w\", err)\n    }\n    return err\n}","preventionTips":["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"],"tags":["cel","filter","parser","comprehension"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}