usememos/memos · error
expression is not an identifier
Error message
expression is not an identifier
What it means
getIdentName extracts the field name from an expression and only succeeds when the expression is an identifier (exprv1.Expr_IdentExpr). Callers use it for receivers of contains/matches, targets of set operations, and comprehension ranges; when the sub-expression is a call, literal, or select, this error surfaces.
Solutions
- Put a bare schema field where a field is expected: content.contains("x")
- Apply size() inside comparisons only: size(tag) > 1, never as a text-match receiver
- Simplify the left side to a single identifier before the dot
Example fix
// before
content.size().contains("x")
// after
content.contains("x") Defensive patterns
Strategy: validation
Validate before calling
// Go (AST level): assert the receiver is an identifier
if call.GetTarget().GetIdentExpr() == nil {
return errors.New("receiver must be a bare field name")
} Type guard
func isIdent(e *exprv1.Expr) bool { return e.GetIdentExpr() != nil } Prevention
- Write a bare field before the dot in contains/matches calls
- Never chain method calls in the filter DSL
- If you build ASTs, validate receivers are identifiers before compiling
When it happens
Trigger: Filters where a field position holds a non-identifier: content.size().contains("x") (method chained on a call), 5.contains("x"), or (a || b).matches("x").
Common situations: Chaining calls so the receiver is itself a call result; wrapping field names in parentheses or expressions; typos that turn an identifier into a function call.
Related errors
- comparison expects two arguments
- comprehension accumulator must be initialized with a…
- comprehension loop step must be a call expression
- 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/232d3a4bc77f621f.
Report an issue: GitHub.
Appendix: source
Thrown at internal/filter/parser.go:462
return CompareNeq, nil
case "_<_":
return CompareLt, nil
case "_>_":
return CompareGt, nil
case "_<=_":
return CompareLte, nil
case "_>=_":
return CompareGte, nil
default:
return "", errors.Errorf("unsupported comparison operator %q", fn)
}
}
func getIdentName(expr *exprv1.Expr) (string, error) {
if ident := expr.GetIdentExpr(); ident != nil {
return ident.GetName(), nil
}
return "", errors.New("expression is not an identifier")
}
func getConstValue(expr *exprv1.Expr) (interface{}, error) {
v, ok := expr.ExprKind.(*exprv1.Expr_ConstExpr)
if !ok {
return nil, errors.New("expression is not a literal")
}
switch x := v.ConstExpr.ConstantKind.(type) {
case *exprv1.Constant_StringValue:
return v.ConstExpr.GetStringValue(), nil
case *exprv1.Constant_Int64Value:
return v.ConstExpr.GetInt64Value(), nil
case *exprv1.Constant_Uint64Value:
return int64(v.ConstExpr.GetUint64Value()), nil
case *exprv1.Constant_DoubleValue:
return v.ConstExpr.GetDoubleValue(), nil
case *exprv1.Constant_BoolValue:
return v.ConstExpr.GetBoolValue(), nilView on GitHub (pinned to 14d757ce1f)