#syntax package

    AST node types for the Starlark language. Import connect0459/starlark/syntax to work with parsed source trees: inspect nodes, walk the tree, or build ASTs for testing.

    Obtain a File by calling @eval.parse_file; use the node types and walker functions to analyse or transform it.

    #Key types and functions

    SymbolDescription
    FileTop-level parsed file (path + statement list)
    ExprExpression node (17 variants)
    StmtStatement node (12 variants)
    Param / Arg / CompClauseParameter, call argument, comprehension clause
    LiteralVal / BinaryOp / UnaryOp / AugOpLeaf enums
    NodeVisitor wrapper covering all node kinds
    walk_file / walk_stmt / walk_exprDepth-first tree traversal
    stmt_pos / expr_posSource position of any node

    #Quick start

    Building a File directly for tests (bypassing the parser):

    ///|
    test {
    let pos = @errors.Position::new("<test>", 1, 1)
    let stmt = @syntax.Stmt::SPass(pos)
    let file = @syntax.File::new("<test>", [stmt])
    assert_eq(file.path(), "<test>")
    assert_eq(file.stmts().length(), 1)
    }

    Collecting identifiers from an expression node:

    ///|
    test {
    let pos = @errors.Position::new("<e>", 1, 1)
    let lhs = @syntax.Expr::EIdent("x", pos)
    let rhs = @syntax.Expr::EIdent("y", pos)
    let expr = @syntax.Expr::EBinary(lhs, @syntax.BinaryOp::OpAdd, rhs, pos)
    let names : Array[String] = []
    @syntax.walk_expr(expr, fn(node) {
    match node {
    Some(@syntax.Node::NExpr(@syntax.Expr::EIdent(name, _))) =>
    names.push(name)
    _ => ()
    }
    true
    })
    assert_true(names == ["x", "y"])
    }

    Parsing from source requires @eval.parse_file / @eval.parse_expr.

    #Error-position anchoring policy

    When the compiler or resolver emits a diagnostic for an AST node it must choose one token as the anchor position — the source location shown to the user. The rules below are applied across the compilation pipeline (syntax, internal/parser, internal/resolver, internal/compile, eval) and mirror the behaviour of the starlark-go reference implementation.

    Error classAnchor tokenHow to obtain it
    Assignment target is not assignable (e.g. f() = 2, (a or b) = 1)Leftmost token of the LHS expressionsyntax.start(lhs)
    Augmented-assignment target is not assignableLeftmost token of the LHS expressionsyntax.start(lhs)
    Dict key errors in literals and comprehensions (unhashable key, duplicate key)Colon token separating the key from its valuecolon_pos from EDict/EDictComp; see note below
    def/lambda parameter is not an identifierScanner cursor (one position past the bad token)Parser::scanner_error → scanner.position()
    Positional argument appears after *args, **kwargs, or a keyword argumentLeftmost token of the misplaced argument expressionsyntax.start(arg_expr)

    #Position accessors

    Four mechanisms cover all anchoring sites above:

    • syntax.start(e) — walks into EBinary, ECall, ESlice, EIndex, and EDot sub-expressions to reach the leftmost token of a compound expression. Use this whenever the error should point at the beginning of a possibly-compound expression (LHS of assignments, misplaced arguments).
    • expr_pos(e) — returns the position stored in the node's own slot (operator, opening bracket, or other delimiter). Use this when the error is about the operator or delimiter itself rather than an operand.
    • Parser::scanner_error(msg) — uses scanner.position(), which is one past the current token. Use this for unexpected-token parse errors that must match starlark-go's scanner cursor convention.
    • set_pos(colon) in internal/compile — dict-literal and dict-comprehension key errors use a different mechanism: the compiler calls set_pos(colon_pos) before emitting the dict-insertion opcode (SetDictUniq / SetDict), recording the colon as the error anchor. The error itself is raised at runtime by the eval package using that recorded position. Both EDict pairs and EDictComp store a colon_pos; compile uses it in both code paths.

    #Adding a new anchoring site

    1. Identify which rule class the new error falls into (table above).
    2. Choose the matching mechanism (syntax.start, expr_pos, scanner_error, or set_pos in compile).
    3. Add a conformance test that checks the reported column against the expected source position so regressions are caught automatically.

    #API reference

    #Functions

    FunctionSignatureDescription
    walk_file(File, (Node?) -> Bool) -> UnitDepth-first traversal of a file; visitor called with Some(node) on entry and None on exit after each parent's last child; return false to stop descending
    walk_stmt(Stmt, (Node?) -> Bool) -> UnitDepth-first traversal of a statement; same Some/None entry/exit contract as walk_file
    walk_expr(Expr, (Node?) -> Bool) -> UnitDepth-first traversal of an expression; same Some/None entry/exit contract as walk_file
    stmt_pos(Stmt) -> @errors.PositionSource position of a statement node
    expr_pos(Expr) -> @errors.PositionSource position of an expression node (node's own slot: operator, bracket, etc.)
    start(Expr) -> @errors.PositionPosition of the leftmost token in an expression (walks into sub-expressions for compound LHS nodes)

    #File

    MethodSignatureDescription
    File::new(String, Array[Stmt])-> FileConstruct from a path and statements
    path()-> StringSource path
    stmts()-> Array[Stmt]Top-level statements

    #AST node enums

    Every node carries a trailing @errors.Position.

    ///|
    pub(all) enum Expr {
    EIdent(String, @errors.Position)
    ELiteral(LiteralVal, @errors.Position)
    EUnary(UnaryOp, Expr, @errors.Position)
    EBinary(Expr, BinaryOp, Expr, @errors.Position)
    ECond(Expr, Expr, Expr, @errors.Position) // cond, then, else (surface: then if cond else else)
    EIndex(Expr, Expr, @errors.Position) // a[i]
    ESlice(Expr, Expr?, Expr?, Expr?, @errors.Position) // a[start:end:step]
    EDot(Expr, String, @errors.Position) // x.attr
    ECall(Expr, Array[Arg], @errors.Position) // f(args…)
    EList(Array[Expr], @errors.Position)
    ETuple(Array[Expr], @errors.Position)
    EDict(Array[(Expr, Expr, @errors.Position)], @errors.Position) // (key, val, colon_pos)
    ESet(Array[Expr], @errors.Position)
    ELambda(Array[Param], Expr, @errors.Position)
    EListComp(Expr, Array[CompClause], @errors.Position)
    ESetComp(Expr, Array[CompClause], @errors.Position)
    EDictComp(Expr, Expr, Array[CompClause], @errors.Position, @errors.Position) // (key, val, clauses, colon_pos, brace_pos)
    }

    ///|
    pub(all) enum Stmt {
    SExpr(Expr)
    SAssign(Expr, Expr, @errors.Position)
    SAugAssign(Expr, AugOp, Expr, @errors.Position)
    SIf(Expr, Array[Stmt], Array[Stmt], @errors.Position)
    SFor(Expr, Expr, Array[Stmt], @errors.Position)
    SWhile(Expr, Array[Stmt], @errors.Position)
    SDef(String, Array[Param], Array[Stmt], @errors.Position)
    SReturn(Expr?, @errors.Position)
    SBreak(@errors.Position)
    SContinue(@errors.Position)
    SPass(@errors.Position)
    SLoad(String, Array[(String, String, @errors.Position)], @errors.Position)
    }

    ///|
    pub(all) enum Param {
    ParamIdent(String, @errors.Position) // x
    ParamDefault(String, Expr, @errors.Position) // x=expr
    ParamStarBare(@errors.Position) // *
    ParamStarIdent(String, @errors.Position) // *args
    ParamKwIdent(String, @errors.Position) // **kwargs
    }

    ///|
    pub(all) enum Arg {
    ArgPos(Expr) // positional
    ArgKw(String, Expr, @errors.Position) // name=expr
    ArgStarArgs(Expr) // *args
    ArgKwArgs(Expr) // **kwargs
    }

    ///|
    pub(all) enum CompClause {
    ClauseFor(Expr, Expr, @errors.Position) // for target in iterable
    ClauseIf(Expr, @errors.Position) // if guard
    }

    ///|
    pub(all) enum LiteralVal {
    LitNone
    LitBool(Bool)
    LitInt(BigInt)
    LitFloat(Double)
    LitString(String)
    LitBytes(Bytes)
    }

    ///|
    pub(all) enum BinaryOp {
    OpAdd
    OpSub
    OpMul
    OpDiv
    OpFloorDiv
    OpMod
    OpBitAnd
    OpBitOr
    OpBitXor
    OpLShift
    OpRShift
    OpEq
    OpNe
    OpLt
    OpLe
    OpGt
    OpGe
    OpIn
    OpNotIn
    OpAnd
    OpOr
    }

    ///|
    pub(all) enum UnaryOp {
    OpPlus
    OpMinus
    OpBitNot
    OpNot
    }

    ///|
    pub(all) enum AugOp {
    AugAdd
    AugSub
    AugMul
    AugDiv
    AugFloorDiv
    AugMod
    AugBitAnd
    AugBitOr
    AugBitXor
    AugLShift
    AugRShift
    }

    // Visitor wrapper passed to walk_*; one variant per node kind.

    ///|
    pub(all) enum Node {
    NFile(File)
    NStmt(Stmt)
    NExpr(Expr)
    NParam(Param)
    NArg(Arg)
    NCompClause(CompClause)
    }

    #walk_file example

    ///|
    test {
    let pos = @errors.Position::new("<walk>", 1, 1)
    let lhs = @syntax.Expr::EIdent("x", pos)
    let rhs = @syntax.Expr::EIdent("y", pos)
    let expr = @syntax.Expr::EBinary(lhs, @syntax.BinaryOp::OpAdd, rhs, pos)
    let stmt = @syntax.Stmt::SExpr(expr)
    let file = @syntax.File::new("<walk>", [stmt])
    let idents : Array[String] = []
    @syntax.walk_file(file, fn(node) {
    match node {
    Some(@syntax.Node::NExpr(@syntax.Expr::EIdent(name, _))) =>
    idents.push(name)
    _ => ()
    }
    true
    })
    assert_true(idents.contains("x"))
    assert_true(idents.contains("y"))
    }

    #expr_pos example

    ///|
    test {
    let pos = @errors.Position::new("<expr>", 3, 5)
    let expr = @syntax.Expr::EIdent("a", pos)
    let got = @syntax.expr_pos(expr)
    assert_eq(got.filename(), "<expr>")
    assert_eq(got.line(), 3)
    }

    Arg

    Represents a single argument at a call site.

    Variants:

    • ArgPos : A positional argument expr.
    • ArgKw : A keyword argument name = expr.
    • ArgStarArgs : A *expr unpacked positional argument.
    • ArgKwArgs : A **expr unpacked keyword argument.

    ArgKw, ArgStarArgs, and ArgKwArgs carry the source Position of the argument token.

    AugOp

    pub(all) enum AugOp {
    AugAdd
    AugSub
    AugMul
    AugDiv
    AugFloorDiv
    AugMod
    AugBitAnd
    AugBitOr
    AugBitXor
    AugLShift
    AugRShift
    }

    Represents an augmented-assignment operator in the Starlark AST.

    Each variant corresponds to one of the +=, -=, *=, /=, //=, %=, &=, |=, ^=, <<=, >>= forms.

    BinaryOp

    pub(all) enum BinaryOp {
    OpAdd
    OpSub
    OpMul
    OpDiv
    OpFloorDiv
    OpMod
    OpBitAnd
    OpBitOr
    OpBitXor
    OpLShift
    OpRShift
    OpEq
    OpNe
    OpLt
    OpLe
    OpGt
    OpGe
    OpIn
    OpNotIn
    OpAnd
    OpOr
    }

    Represents a binary operator in the Starlark AST.

    Covers arithmetic, bitwise, comparison, membership, and logical operators.

    CompClause

    Represents a single clause in a comprehension expression.

    Variants:

    • ClauseFor : A for lhs in rhs iteration clause.
    • ClauseIf : A filtering if cond clause.

    Each variant carries the source Position of the clause keyword.

    Expr

    Represents an expression node in the Starlark AST.

    Every variant carries a source Position as its last field for error reporting. Variants:

    • EIdent : An identifier reference.
    • ELiteral : A literal value.
    • EUnary : A unary operation.
    • EBinary : A binary operation.
    • ECond : A conditional expression t if cond else f.
    • EIndex : A subscript expression obj[key].
    • ESlice : A slice expression obj[lo:hi:step]; absent bounds are None.
    • EDot : An attribute access obj.attr.
    • ECall : A function call fn(args...).
    • EList : A list display [...].
    • ETuple : A tuple display (...).
    • EDict : A dict display {k: v, ...}.
    • ESet : A set display {...}.
    • ELambda : A lambda expression lambda params: body.
    • EListComp : A list comprehension [expr for ...].
    • ESetComp : A set comprehension {expr for ...}.
    • EDictComp : A dict comprehension {k: v for ...}.

    File

    pub struct File {
    // private fields
    }

    A parsed Starlark source file: its filename and top-level statement list.

    File::new

    fn File::new(path : String, stmts : Array[Stmt]) -> File

    Constructs a File with the given source path and top-level statement list.

    Parameters:

    • path : The source file path recorded in the AST.
    • stmts : The top-level statements parsed from the file.

    Returns a new File AST node.

    File::path

    fn File::path(self : File) -> String

    Returns the source file path.

    Parameters:

    • self : The file node to inspect.

    Returns the path string recorded in this File.

    File::stmts

    fn File::stmts(self : File) -> Array[Stmt]

    Returns the top-level statement list.

    Parameters:

    • self : The file node to inspect.

    Returns the array of top-level Stmt nodes in this file.

    LiteralVal

    pub(all) enum LiteralVal {
    LitInt(
    BigInt
    )
    LitFloat(Double)
    LitString(String)
    LitBytes(Bytes)
    }

    Represents a literal value in the Starlark AST.

    Variants:

    • LitInt : An integer literal.
    • LitFloat : A floating-point literal.
    • LitString : A string literal.
    • LitBytes : A bytes literal.

    Node

    pub(all) enum Node {
    NFile(File)
    NStmt(Stmt)
    NExpr(Expr)
    NParam(Param)
    NArg(Arg)
    NCompClause(CompClause)
    }

    A tagged union of every AST node kind, used as the argument to the visitor function passed to walk_file/walk_stmt/walk_expr.

    Param

    Represents a single parameter in a function definition.

    Variants:

    • ParamIdent : A plain positional parameter name.
    • ParamDefault : A parameter with a default value name = expr.
    • ParamStarBare : A bare * separator (no name).
    • ParamStarIdent : A *name variadic positional parameter.
    • ParamKwIdent : A **name variadic keyword parameter.

    Each variant carries the source Position of the parameter token.

    Stmt

    Represents a statement node in the Starlark AST.

    Every variant (except SExpr) carries a source Position as its last field for error reporting. Variants:

    • SExpr : An expression used as a statement.
    • SAssign : A simple assignment lhs = rhs.
    • SAugAssign : An augmented assignment lhs op= rhs.
    • SIf : An if / else block; the else branch may be empty.
    • SFor : A for loop.
    • SWhile : A while loop.
    • SDef : A function definition def name(params): body.
    • SReturn : A return statement; the expression is optional.
    • SBreak : A break statement.
    • SContinue : A continue statement.
    • SPass : A pass statement.
    • SLoad : A load statement; each binding is (local, orig, pos).

    UnaryOp

    pub(all) enum UnaryOp {
    OpPlus
    OpMinus
    OpBitNot
    OpNot
    }

    Represents a unary operator in the Starlark AST.

    Variants:

    • OpPlus : Unary +.
    • OpMinus : Unary -.
    • OpBitNot : Bitwise complement ~.
    • OpNot : Logical negation not.

    expr_pos

    Returns the source position of an expression node.

    Parameters:

    • e : The expression node to inspect.

    Returns the Position stored in the expression's position slot.

    flatten_left_chain

    fn[A] flatten_left_chain(expr : Expr, peel : (Expr) -> (Expr, A)?) -> (Expr, Array[A])

    Iteratively peels a left-associative chain of Expr nodes, collecting a payload from each step until peel returns None.

    Any walker that uses recursive descent over Expr nodes must handle the following left-recursive Expr variants iteratively (not recursively) to prevent stack overflow on deeply chained expressions:

    • EBinary — left-associative binary operations (arithmetic, comparison, in/not in, and, or). OpAnd/OpOr are encoded as EBinary with those operators, not as separate variants. Their short-circuit compilation interleaves label emission with the flatten loop, so compile_and/compile_or keep their own iterative loops rather than calling flatten_left_chain.
    • EIndex — chained subscript: a[i][j][k].
    • EDot — chained attribute access: a.b.c.d.

    flatten_left_chain provides the shared primitive for walkers that collect payloads from each step. Its peel callback returns Some((left_child, payload)) when the current node matches the target kind, or None to stop.

    Returns (leftmost, payloads) where payloads are in left-to-right (source-code) order.

    start

    Returns the position of the leftmost token in an expression.

    Unlike expr_pos, which returns the position stored in the node's own slot (operator, bracket, etc.), start walks into sub-expressions to find the leftmost token of the whole expression. Used to anchor error messages at the beginning of a compound LHS (e.g. foobar in foobar() = 2). See the "Error-position anchoring policy" section in README.mbt.md for the full set of anchoring rules.

    Parameters:

    • e : The expression node to inspect.

    Returns the Position of the leftmost token.

    stmt_pos

    Returns the source position of a statement node.

    Parameters:

    • s : The statement node to inspect.

    Returns the Position stored in the statement's position slot.

    walk_expr

    fn walk_expr(expr : Expr, f : (Node?) -> Bool) -> Unit

    Performs a depth-first walk of an expression and all its sub-expressions.

    Parameters:

    • expr : The expression node to walk.
    • f : Visitor callback; receives Some(node) on entry and None on exit of each parent. Return false to skip a subtree.

    walk_file

    fn walk_file(file : File, f : (Node?) -> Bool) -> Unit

    Performs a depth-first walk of a parsed Starlark file, calling f before descending into each node. If f returns false, the subtree is skipped. f(None) is called after the last child of every parent.

    Parameters:

    • file : The parsed file whose nodes are visited.
    • f : Visitor callback; receives Some(node) on entry and None on exit of each parent. Return false to skip a subtree.

    walk_stmt

    fn walk_stmt(stmt : Stmt, f : (Node?) -> Bool) -> Unit

    Performs a depth-first walk of a statement and all its sub-expressions.

    Parameters:

    • stmt : The statement node to walk.
    • f : Visitor callback; receives Some(node) on entry and None on exit of each parent. Return false to skip a subtree.