Module

compiler.expressions

Expression compilation for Kida compiler.

Provides mixin for compiling Kida expression AST nodes to Python AST expressions.

Uses inline TYPE_CHECKING declarations for host attributes. See: plan/rfc-mixin-protocol-typing.md

Classes

ExpressionCompilationMixin 36
Mixin for compiling expressions. Host attributes and cross-mixin dependencies are declared via inl…

Mixin for compiling expressions.

Host attributes and cross-mixin dependencies are declared via inline TYPE_CHECKING blocks.

Attributes

Name Type Description
_EXPR_DISPATCH ClassVar[dict[str, str]]
_STORE_METHODS frozenset[str]

Methods

Internal Methods 34
_precomputed_ref 1 ast.Name
Return an ``ast.Name`` referencing a precomputed module-level binding. Non-con…
def _precomputed_ref(self, value: object) -> ast.Name

Return anast.Namereferencing a precomputed module-level binding.

Non-constant-safe values (dict, list, set, custom objects) cannot be stored inast.Constantnodes. Instead they are collected during compilation and injected into theexec() namespace as _pc_N.

Parameters
Name Type Description
value
Returns
ast.Name
_get_filter_suggestion 1 str | None
Find closest matching filter name for typo suggestions.
def _get_filter_suggestion(self, name: str) -> str | None
Parameters
Name Type Description
name
Returns
str | None
_get_test_suggestion 1 str | None
Find closest matching test name for typo suggestions.
def _get_test_suggestion(self, name: str) -> str | None
Parameters
Name Type Description
name
Returns
str | None
_make_deferred_lambda 1 ast.Lambda
Wrap an expression in a zero-arg lambda for deferred evaluation. Used by ``_is…
def _make_deferred_lambda(self, expr: ast.expr) -> ast.Lambda

Wrap an expression in a zero-arg lambda for deferred evaluation.

Used by_is_defined, _default_safe, and _null_coalesce to catchUndefinedErrorat runtime without evaluating the expression eagerly.

Parameters
Name Type Description
expr
Returns
ast.Lambda
_is_potentially_string 1 bool
Check if node could produce a string value (macro call, filter chain). Used to…
def _is_potentially_string(self, node: Node) -> bool

Check if node could produce a string value (macro call, filter chain).

Used to determine when numeric coercion is needed for arithmetic operations. Recursively checks nested expressions to catch Filter nodes inside parentheses.

This handles cases like (a | length) + (b | length) where the left/right operands are Filter nodes that need numeric coercion.

Parameters
Name Type Description
node
Returns
bool
_wrap_coerce_numeric 1 ast.expr
Wrap expression in _coerce_numeric() call for arithmetic safety. Ensures that …
def _wrap_coerce_numeric(self, expr: ast.expr) -> ast.expr

Wrap expression in _coerce_numeric() call for arithmetic safety.

Ensures that Markup objects (from macros) are converted to numbers before arithmetic operations, preventing string multiplication.

Parameters
Name Type Description
expr
Returns
ast.expr
_compile_expr 2 ast.expr
Compile expression node to Python AST expression. O(1) dict dispatch by ``type…
def _compile_expr(self, node: Node, store: bool = False) -> ast.expr

Compile expression node to Python AST expression.

O(1) dict dispatch bytype(node).__name__.

Parameters
Name Type Description
node
store Default:False
Returns
ast.expr
_compile_const 1 ast.expr
Compile constant literal.
def _compile_const(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_name 2 ast.expr
Compile variable reference.
def _compile_name(self, node: Node, *, store: bool = False) -> ast.expr
Parameters
Name Type Description
node
store Default:False
Returns
ast.expr
_compile_tuple 2 ast.expr
Compile tuple expression.
def _compile_tuple(self, node: Node, *, store: bool = False) -> ast.expr
Parameters
Name Type Description
node
store Default:False
Returns
ast.expr
_compile_list 1 ast.expr
Compile list expression.
def _compile_list(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_list_comp 1 ast.expr
Compile list comprehension.
def _compile_list_comp(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_dict 1 ast.expr
Compile dict expression.
def _compile_dict(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_getattr 1 ast.expr
Compile attribute access (obj.attr).
def _compile_getattr(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_getitem 1 ast.expr
Compile subscript access (obj[key]).
def _compile_getitem(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_slice 1 ast.expr
Compile slice expression (start:stop:step).
def _compile_slice(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_test 1 ast.expr
Compile test expression (is defined, is none, etc.).
def _compile_test(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_func_call 1 ast.expr
Compile function call expression.
def _compile_func_call(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_filter 1 ast.expr
Compile filter expression (value | filter_name).
def _compile_filter(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_binop 1 ast.expr
Compile binary operation.
def _compile_binop(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_unaryop 1 ast.expr
Compile unary operation.
def _compile_unaryop(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_compare 1 ast.expr
Compile comparison expression.
def _compile_compare(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_boolop 1 ast.expr
Compile boolean operation (and/or).
def _compile_boolop(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_cond_expr 1 ast.expr
Compile conditional (ternary) expression.
def _compile_cond_expr(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_await 1 ast.expr
Compile await expression.
def _compile_await(self, node: Node) -> ast.expr
Parameters
Name Type Description
node
Returns
ast.expr
_compile_null_coalesce 1 ast.expr
Compile a ?? b to handle both None and undefined variables. Uses _null_coalesc…
def _compile_null_coalesce(self, node: NullCoalesce) -> ast.expr

Compile a ?? b to handle both None and undefined variables.

Uses _null_coalesce helper to catch UndefinedError for undefined variables. Part of RFC: kida-modern-syntax-features.

The helper is called as:

_null_coalesce(lambda: a, lambda: b)

This allows:

  • a ?? b to return b if a is undefined (UndefinedError)
  • a ?? b to return b if a is None
  • a ?? b to return a if a is any other value (including falsy: 0, '', [])
Parameters
Name Type Description
node
Returns
ast.expr
_compile_optional_getattr 1 ast.expr
Compile obj?.attr using walrus operator to avoid double evaluation. obj?.attr …
def _compile_optional_getattr(self, node: OptionalGetattr) -> ast.expr

Compile obj?.attr using walrus operator to avoid double evaluation.

obj?.attr compiles to: '' if (_oc := obj) is None else (_oc_val := _getattr_none(_oc, 'attr')) if _oc_val is not None else ''

The double check ensures that:

  1. If obj is None, return ''
  2. If obj.attr is None, return '' (for output) but preserve None for ??

For null coalescing to work, we need a different approach: the optional chain preserves None so ?? can check it, but for direct output, None becomes ''.

Actually, we return None but rely on the caller to handle None → '' conversion. For output, the expression is wrapped differently.

Simplified: Return None when short-circuiting, let output handle conversion.

Part of RFC: kida-modern-syntax-features.

Parameters
Name Type Description
node
Returns
ast.expr
_compile_optional_getitem 1 ast.expr
Compile obj?[key] using walrus operator to avoid double evaluation. obj?[key] …
def _compile_optional_getitem(self, node: OptionalGetitem) -> ast.expr

Compile obj?[key] using walrus operator to avoid double evaluation.

obj?[key] compiles to: None if (_oc := obj) is None else _oc[key]

Part of RFC: kida-modern-syntax-features.

Parameters
Name Type Description
node
Returns
ast.expr
_compile_optional_method_call 3 ast.expr
Compile obj?.method(*args, **kwargs) with short-circuit. When obj is None or o…
def _compile_optional_method_call(self, opt_getattr: OptionalGetattr, args: Sequence[Any], kwargs: dict[str, Any]) -> ast.expr

Compile obj?.method(*args, **kwargs) with short-circuit.

When obj is None or obj.method is UNDEFINED, return None without calling. Uses _optional_call(callee, *args, **kwargs) helper.

Parameters
Name Type Description
opt_getattr
args
kwargs
Returns
ast.expr
_compile_range 1 ast.expr
Compile range literal to range() call. 1..10 → range(1, 11) # inclusiv…
def _compile_range(self, node: Range) -> ast.expr

Compile range literal to range() call.

1..10 → range(1, 11) # inclusive 1...11 → range(1, 11) # exclusive 1..10 by 2 → range(1, 11, 2)

Part of RFC: kida-modern-syntax-features.

Parameters
Name Type Description
node
Returns
ast.expr
_compile_inlined_filter 1 ast.Call
Compile inlined filter to direct method call. Generates: _str(value).method(*a…
def _compile_inlined_filter(self, node: InlinedFilter) -> ast.Call

Compile inlined filter to direct method call.

Generates: _str(value).method(*args)

This replaces filter dispatch overhead with a direct method call, providing ~5-10% speedup for filter-heavy templates.

Parameters
Name Type Description
node
Returns
ast.Call
_compile_optional_filter 1 ast.expr
Compile expr ?| filter — skip filter if value is None. expr ?| upper compiles…
def _compile_optional_filter(self, node: Any) -> ast.expr

Compile expr ?| filter — skip filter if value is None.

expr ?| upper compiles to: None if (_of_N := expr) is None else _filters'upper'

Parameters
Name Type Description
node
Returns
ast.expr
_compile_pipeline 1 ast.expr
Compile pipeline: expr |> filter1 |> filter2. Pipelines compile to nested filt…
def _compile_pipeline(self, node: Pipeline) -> ast.expr

Compile pipeline: expr |> filter1 |> filter2.

Pipelines compile to nested filter calls using the _filters dict, exactly like regular filter chains. The difference is purely syntactic.

expr |> a |> b(x) → _filters['b'](_filters'a', x)

Validates filter existence at compile time (same as Filter nodes).

Parameters
Name Type Description
node
Returns
ast.expr
_compile_safe_pipeline 1 ast.expr
Compile safe pipeline: expr ?|> filter1 ?|> filter2. None-propagating: each st…
def _compile_safe_pipeline(self, node: Any) -> ast.expr

Compile safe pipeline: expr ?|> filter1 ?|> filter2.

None-propagating: each step checks for None before applying the filter.

expr ?|> a ?|> b(x) compiles to: _sp_2 = None if (_sp_1 := expr) is None else _filters'a' None if _sp_2 is None else _filters['b'](_sp_2, x)

Parameters
Name Type Description
node
Returns
ast.expr

Functions

_is_constant_safe 1 bool
Return True if *value* can be stored in an ``ast.Constant`` node.
def _is_constant_safe(value: object) -> bool
Parameters
Name Type Description
value object
Returns
bool