{"record":{"id":"4901f3bbac0b84ec","repo":"actualbudget/actual","slug":"a-non-literal-null-doesn-t-make-sense","errorCode":null,"errorMessage":"A non-literal null doesn't make sense","messagePattern":"A non-literal null doesn't make sense","errorType":"exception","errorClass":"CompileError","httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/aql/compiler.ts","lineNumber":254,"sourceCode":"    ) {\n      throw new Error(\n        `Parameter \"${param.paramName}\" can't convert to ${type} (already inferred as ${existingType})`,\n      );\n    }\n  } else {\n    param.paramType = type;\n  }\n}\n\nfunction castInput(state, expr, type) {\n  if (expr.type === type) {\n    return expr;\n  } else if (expr.type === 'param') {\n    inferParam(expr, type);\n    return typed(expr.value, type);\n  } else if (expr.type === 'null') {\n    if (!expr.literal) {\n      throw new CompileError(\"A non-literal null doesn't make sense\");\n    }\n\n    if (type === 'boolean') {\n      return typed(0, 'boolean', { literal: true });\n    }\n    return expr;\n  }\n\n  // These are all things that can be safely casted automatically\n  if (type === 'date') {\n    if (expr.type === 'string') {\n      if (expr.literal) {\n        return parseDate(expr.value) || badDateFormat(expr.value, 'date');\n      } else {\n        throw new CompileError(\n          'Casting string fields to dates is not supported',\n        );\n      }","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/aql/compiler.ts#L236-L272","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass the JavaScript value null through the public query builder (e.g. filter({ payee_id: null })) instead of hand-building the AST node.","If you must build the node, set literal: true on the null expression.","Replace hand-built null comparisons with the appropriate query-builder operators."],"exampleFix":"// before\n{ type: 'null', literal: false }\n// after\n{ type: 'null', literal: true } // or use q/filter with null directly","handlingStrategy":"validation","validationCode":"function assertLiteralNull(node) {\n  if (node && node.type === 'null' && node.literal !== true) {\n    throw new Error('Null expressions must be literal; pass null via the query builder instead');\n  }\n}","typeGuard":"function isLiteralNull(node) {\n  return node != null && typeof node === 'object' && node.type === 'null' && node.literal === true;\n}","tryCatchPattern":"try {\n  runCompiledExpr(expr);\n} catch (e) {\n  if (e.message.includes(\"non-literal null\")) {\n    // rebuild expression via public API with plain null value\n  } else throw e;\n}","preventionTips":["Use the public query builder (filter with null) instead of hand-built AST nodes","If building ASTs manually, always set literal: true on null nodes","Keep expression-construction code in one tested helper"],"tags":["aql","null","ast","compile-error"],"backgroundTag":"non-literal-null-expression","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}