actualbudget/actual · error · CompileError

A non-literal null doesn't make sense

Error message

A non-literal null doesn't make sense

What it means

In castInput (compiler.ts:252), a null expression is only meaningful as an explicit literal (e.g. from the $lightning/$null literal or null passed as a literal value). A non-literal 'null' AST node has no column/parameter backing, so the compiler refuses it with this CompileError.

Source

Thrown at packages/loot-core/src/server/aql/compiler.ts:254

    ) {
      throw new Error(
        `Parameter "${param.paramName}" can't convert to ${type} (already inferred as ${existingType})`,
      );
    }
  } else {
    param.paramType = type;
  }
}

function castInput(state, expr, type) {
  if (expr.type === type) {
    return expr;
  } else if (expr.type === 'param') {
    inferParam(expr, type);
    return typed(expr.value, type);
  } else if (expr.type === 'null') {
    if (!expr.literal) {
      throw new CompileError("A non-literal null doesn't make sense");
    }

    if (type === 'boolean') {
      return typed(0, 'boolean', { literal: true });
    }
    return expr;
  }

  // These are all things that can be safely casted automatically
  if (type === 'date') {
    if (expr.type === 'string') {
      if (expr.literal) {
        return parseDate(expr.value) || badDateFormat(expr.value, 'date');
      } else {
        throw new CompileError(
          'Casting string fields to dates is not supported',
        );
      }

View on GitHub (pinned to d4334cb6e6)

Solutions

  1. Pass the JavaScript value null through the public query builder (e.g. filter({ payee_id: null })) instead of hand-building the AST node.
  2. If you must build the node, set literal: true on the null expression.
  3. Replace hand-built null comparisons with the appropriate query-builder operators.

Example fix

// before
{ type: 'null', literal: false }
// after
{ type: 'null', literal: true } // or use q/filter with null directly
Defensive patterns

Strategy: validation

Validate before calling

function assertLiteralNull(node) {
  if (node && node.type === 'null' && node.literal !== true) {
    throw new Error('Null expressions must be literal; pass null via the query builder instead');
  }
}

Type guard

function isLiteralNull(node) {
  return node != null && typeof node === 'object' && node.type === 'null' && node.literal === true;
}

Try / catch

try {
  runCompiledExpr(expr);
} catch (e) {
  if (e.message.includes("non-literal null")) {
    // rebuild expression via public API with plain null value
  } else throw e;
}

Prevention

When it happens

Trigger: Constructing an expression AST by hand with { type: 'null', literal: false } and feeding it through val/compileOp/compileFunction; using internal expression-builder APIs in a way that produces an unmarked null node.

Common situations: Custom query-building code that creates AST nodes directly instead of using the public query builder; older internal tooling producing null nodes without the literal flag after compiler changes.

Related errors


AI-assisted analysis of actualbudget/actual@d4334cb6e6 (2026-08-29). Data as JSON: /api/errors/4901f3bbac0b84ec. Report an issue: GitHub.