{"record":{"id":"a04403ef0fed02d5","repo":"usememos/memos","slug":"not-expects-exactly-one-argument","errorCode":null,"errorMessage":"NOT expects exactly one argument","messagePattern":"NOT expects exactly one argument","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":502,"sourceCode":"\t\treturn nil, errors.Errorf(\"unsupported constant %T\", x)\n\t}\n}\n\nfunc evaluateBool(call *exprv1.Expr_Call) (bool, bool, error) {\n\tval, ok, err := evaluateBoolExpr(&exprv1.Expr{ExprKind: &exprv1.Expr_CallExpr{CallExpr: call}})\n\treturn val, ok, err\n}\n\nfunc evaluateBoolExpr(expr *exprv1.Expr) (bool, bool, error) {\n\tif literal, err := getConstValue(expr); err == nil {\n\t\tif b, ok := literal.(bool); ok {\n\t\t\treturn b, true, nil\n\t\t}\n\t\treturn false, false, nil\n\t}\n\tif call := expr.GetCallExpr(); call != nil && call.Function == \"!_\" {\n\t\tif len(call.Args) != 1 {\n\t\t\treturn false, false, errors.New(\"NOT expects exactly one argument\")\n\t\t}\n\t\tval, ok, err := evaluateBoolExpr(call.Args[0])\n\t\tif err != nil || !ok {\n\t\t\treturn false, false, err\n\t\t}\n\t\treturn !val, true, nil\n\t}\n\treturn false, false, nil\n}\n\n// evaluateNumeric constant-folds an expression to an int64 measured in seconds:\n// timestamps and `now` fold to Unix epoch seconds, durations fold to a number of\n// seconds, and the two combine through standard arithmetic. CEL has already\n// type-checked the operand combinations, so the folded int math is well-formed.\nfunc evaluateNumeric(expr *exprv1.Expr, now time.Time) (int64, bool, error) {\n\tif literal, err := getConstValue(expr); err == nil {\n\t\tswitch v := literal.(type) {\n\t\tcase int64:","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L484-L520","documentation":"evaluateBoolExpr constant-folds boolean expressions, including CEL's NOT operator '!_'. It requires exactly one operand; a malformed AST with a different arity (only producible by hand-built or library-generated expressions, since the CEL parser itself enforces unary arity) triggers this error.","triggerScenarios":"Programmatically constructing an exprv1.Expr_Call with Function \"!_\" and zero or multiple args, or a custom CEL AST builder emitting broken NOT nodes. Plain filter strings like !pinned parse correctly and never hit this.","commonSituations":"Code that synthesizes or transforms CEL ASTs (macros, optimizers) instead of parsing filter text; partial AST rewrites that drop operands.","solutions":["If building ASTs by hand, give '!_' exactly one argument","Prefer passing filter strings through the normal CEL parser rather than constructing Expr protos directly","Round-trip the AST through cel.Parse of its string form to validate structure"],"exampleFix":"// before (hand-built proto)\nnotCall.Args = nil\n\n// after\nnotCall.Args = []*exprv1.Expr{operand}","handlingStrategy":"validation","validationCode":"// Go (AST builder): enforce unary arity when emitting NOT\nif fn == \"!_\" && len(args) != 1 {\n    return errors.New(\"NOT needs exactly one operand\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer parsing filter strings over hand-building Expr protos","Validate arity invariants after every AST rewrite pass","Round-trip ASTs through cel.Parse to canonicalize structure"],"tags":["cel","ast","not-operator","internal"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}