usememos/memos · error

comprehension predicate must be a function call

Error message

comprehension predicate must be a function call

What it means

After extracting the predicate node from the loop step, the parser requires it to be a function call (==, startsWith, endsWith, contains). A predicate that is a bare identifier, constant, select expression, or other node type cannot be translated to a SQL predicate and triggers this error.

Source

Thrown at internal/filter/parser.go:900

	}

	// 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 {
		return nil, errors.New("comprehension predicate must be a function call")
	}

	// Handle different predicate functions
	switch predicateCall.Function {
	case "_==_":
		return buildEqualsPredicate(predicateCall, comp.IterVar)
	case "startsWith":
		return buildStartsWithPredicate(predicateCall, comp.IterVar)
	case "endsWith":
		return buildEndsWithPredicate(predicateCall, comp.IterVar)
	case "contains":
		return buildContainsPredicate(predicateCall, comp.IterVar)
	default:
		return nil, errors.Errorf(`unsupported predicate function %q in comprehension (supported: ==, startsWith, endsWith, contains)`, predicateCall.Function)
	}
}

// buildEqualsPredicate extracts the value from t == "value".

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Write the predicate as an explicit call on the iteration variable, e.g. tag.exists(t, t == "work") or tag.exists(t, t.contains("work"))
  2. Do not use bare identifiers as predicates; always compare against a string constant

Example fix

// before
`tag.exists(t, t)`

// after
`tag.exists(t, t == "work")`
Defensive patterns

Strategy: validation

Validate before calling

// Require every comprehension predicate to call ==, startsWith, endsWith, or contains
var predFn = regexp.MustCompile(`\.\s*(exists|all|exists_one)\s*\(\s*\w+\s*,\s*([^)]+)\)`)

func validatePredicates(expr string) error {
    for _, m := range predFn.FindAllStringSubmatch(expr, -1) {
        p := m[2]
        ok := strings.Contains(p, "==") || strings.Contains(p, "startsWith") ||
            strings.Contains(p, "endsWith") || strings.Contains(p, "contains")
        if !ok {
            return fmt.Errorf("predicate %q must use ==, startsWith, endsWith, or contains", p)
        }
    }
    return nil
}

Try / catch

if err := r.Render(cond); err != nil {
    if strings.Contains(err.Error(), "predicate must be a function call") {
        return userErr("write the tag predicate as t == \"value\" or t.contains(\"value\")")
    }
    return err
}

Prevention

When it happens

Trigger: tag.exists(t, t) (bare iteration variable), tag.all(t, someFlag) where someFlag is an ident, or any predicate not built from one of the four supported operators.

Common situations: Users write tag.exists(t, t) expecting truthiness filtering, or reference a boolean property of the iterated element instead of comparing it to a constant.

Related errors


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