cube-js/cube · error · UserError

Empty Python atom_expr node: ${node.constructor.name}: ${nod

Error message

Empty Python atom_expr node: ${node.constructor.name}: ${node.getText()}

What it means

Thrown by PythonParser.transpileToJs() when an Atom_exprContext (attribute access / call chain like obj.method()) produces zero children, i.e. the visitor reduced every child away and there is nothing to build an expression from. This is an internal-consistency failure of the transpiler given unusual Python syntax inside atom_expr.

Source

Thrown at packages/cubejs-schema-compiler/src/parser/PythonParser.ts:182

          }
          return t.templateLiteral(children.filter(c => c.type === 'TemplateElement'), children.filter(c => c.type !== 'TemplateElement'));
        } else if (node instanceof Atom_exprContext) {
          if (children.length === 1) {
            return children[0];
          } else if (children.length > 1) {
            let expr = children[0];
            for (let i = 1; i < children.length; i++) {
              if (children[i].call) {
                expr = t.callExpression(expr, children[i].call);
              } else if (children[i].identifier) {
                expr = t.memberExpression(expr, children[i].identifier);
              } else {
                throw new Error(`Unexpected trailer child: ${children[i]}`);
              }
            }
            return expr;
          } else {
            throw new UserError(`Empty Python atom_expr node: ${node.constructor.name}: ${node.getText()}`);
          }
        } else if (node instanceof AtomContext) {
          const name = node.NAME();
          const stringList = node.STRING_list();
          const number = node.NUMBER();

          if (name) {
            return t.identifier(name.getText());
          } else if (stringList && stringList.length) {
            return t.stringLiteral(stringList.map(s => this.stripQuotes(s.getText())).join(''));
          } else if (number) {
            const numText = number.getText();
            const numValue = parseFloat(numText);
            return t.numericLiteral(numValue);
          } else {
            return singleNodeReturn();
          }
        } else if (node instanceof CallArgumentsContext) {

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Locate the code from node.getText() in the error message and simplify it to plain identifier/attribute/call syntax
  2. Remove async/await, yield, or generator constructs from the data model file
  3. Fix any preceding syntax errors in the file (error recovery can create empty nodes)
  4. Report as a bug with the offending snippet if standard syntax triggers it

Example fix

// before (cubes.py)
await cube_config.load()
// after
load(cube_config)
Defensive patterns

Strategy: try-catch

Validate before calling

// Guard degenerate trees by pre-parsing with error collection
const parser = new PythonParser(source);
if (!parser.canParse()) throw new Error('Fix syntax errors before transpiling');

Try / catch

try {
  transpileToJs();
} catch (e) {
  if (e instanceof UserError && e.message.startsWith('Empty Python atom_expr node')) {
    throw new Error(`Unsupported/empty expression in model: ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: parsePythonAndTranspileToJs visits an Atom_exprContext whose visitNode children array is empty — e.g. atom_expr containing only constructs the visitor returns undefined for, or malformed/partially parsed input from a failed ANTLR recovery.

Common situations: Rare; usually follows exotic Python in the data model (await expressions, yield, complex trailers) or a parser error-recovery path that produced a degenerate tree. Appears when compiling cubes.py during schema compile.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/2b99cf016fef5fb5. Report an issue: GitHub.