Module

compiler.partial_eval

Compile-time partial evaluation of template AST.

Transforms a Kida AST by evaluating expressions whose values can be determined from a static context (values known at compile time, not at render time). This replaces dynamic lookups with constant data nodes, enabling more aggressive f-string coalescing and producing templates where static regions are literal strings in bytecode.

Example:

Given static_context = {"site": Site(title="My Blog")}:

Before:  Output(expr=Getattr(Name("site"), "title"))
After:   Data(value="My Blog")

The evaluator is conservative: if any sub-expression cannot be resolved, the entire expression is left untouched. This guarantees that partial evaluation never changes observable behavior.

Integration:

Called by ``Environment._compile()`` between parsing and compilation.
The partially-evaluated AST is then compiled normally by the Compiler,
which sees more Data/Const nodes and produces better coalesced output.

Classes

PartialEvaluator 24
Evaluate static expressions in a Kida AST at compile time. Walks the template AST and replaces exp…

Evaluate static expressions in a Kida AST at compile time.

Walks the template AST and replaces expressions that can be fully resolved from the static context with their computed values.

Methods

evaluate 1 Template
Transform a template AST by evaluating static expressions. Returns a new Templ…
def evaluate(self, template: Template) -> Template

Transform a template AST by evaluating static expressions.

Returns a new Template node with static parts replaced by constants. The original template is not modified.

Parameters
Name Type Description
template
Returns
Template
Internal Methods 23
__init__ 6
def __init__(self, static_context: dict[str, Any], *, escape_func: Any | None = None, pure_filters: frozenset[str] = frozenset(), filter_callables: dict[str, Callable[..., Any]] | None = None, max_eval_depth: int = 100, inline_components: bool = False) -> None
Parameters
Name Type Description
static_context
escape_func Default:None
pure_filters Default:frozenset()
filter_callables Default:None
max_eval_depth Default:100
inline_components Default:False
_try_eval 2 Any
Try to evaluate an expression against the static context. Returns the computed…
def _try_eval(self, expr: Expr, depth: int = 0) -> Any

Try to evaluate an expression against the static context.

Returns the computed value on success, or_UNRESOLVEDif the expression depends on runtime values.

Depth limit prevents stack overflow from deeply nested attribute chains (e.g. a.b.c.d.e... with 500+ levels).

Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_try_eval_filter 2 Any
Evaluate a Filter node when value and args are resolvable. OptionalFilter (``?…
def _try_eval_filter(self, expr: Filter, depth: int = 0) -> Any

Evaluate a Filter node when value and args are resolvable.

OptionalFilter (?|) returns None when the input value is None.

Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_try_eval_pipeline 2 Any
Evaluate a Pipeline node when value and all steps are resolvable. SafePipeline…
def _try_eval_pipeline(self, expr: Pipeline, depth: int = 0) -> Any

Evaluate a Pipeline node when value and all steps are resolvable.

SafePipeline (?|>) propagates None through the chain.

Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_try_eval_listcomp 2 Any
Evaluate a list comprehension when iterable and all parts resolve.
def _try_eval_listcomp(self, expr: ListComp, depth: int = 0) -> Any
Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_try_eval_funccall 2 Any
Evaluate a FuncCall when it targets a safe builtin and all args resolve.
def _try_eval_funccall(self, expr: FuncCall, depth: int = 0) -> Any
Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_try_eval_range 2 Any
Evaluate a Range literal (start..end or start...end).
def _try_eval_range(self, expr: Range, depth: int = 0) -> Any
Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_try_eval_test 2 Any
Evaluate a Test node when the value can be resolved. Handles ``is defined`` / …
def _try_eval_test(self, expr: Test, depth: int = 0) -> Any

Evaluate a Test node when the value can be resolved.

Handlesis defined / is undefinedspecially (they check context membership, not the value itself). All other tests require a resolved value.

Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_eval_binop 3 Any
Evaluate a binary operation with known operands.
staticmethod
def _eval_binop(op: str, left: Any, right: Any) -> Any
Parameters
Name Type Description
op
left
right
Returns
Any
_eval_unaryop 2 Any
Evaluate a unary operation with a known operand.
staticmethod
def _eval_unaryop(op: str, operand: Any) -> Any
Parameters
Name Type Description
op
operand
Returns
Any
_eval_compare 2 Any
Evaluate a comparison chain with known operands.
def _eval_compare(self, expr: Compare, depth: int = 0) -> Any
Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_eval_boolop 2 Any
Evaluate a boolean operation with short-circuit semantics.
def _eval_boolop(self, expr: BoolOp, depth: int = 0) -> Any
Parameters
Name Type Description
expr
depth Default:0
Returns
Any
_transform_body 1 Sequence[Node]
Transform a sequence of nodes, merging adjacent Data nodes.
def _transform_body(self, body: Sequence[Node]) -> Sequence[Node]
Parameters
Name Type Description
body
Returns
Sequence[Node]
_append_and_merge 2
Append a node, merging adjacent Data nodes.
staticmethod
def _append_and_merge(nodes: list[Node], new_node: Node) -> None
Parameters
Name Type Description
nodes
new_node
_transform_node 1 Node | None
Transform a single AST node. Returns the transformed node, or None to remove i…
def _transform_node(self, node: Node) -> Node | None

Transform a single AST node.

Returns the transformed node, or None to remove it.

Parameters
Name Type Description
node
Returns
Node | None
_transform_output 1 Node
Try to evaluate an Output node to a Data node.
def _transform_output(self, node: Output) -> Node
Parameters
Name Type Description
node
Returns
Node
_transform_if 1 Node | None
Try to evaluate an If node's test at compile time.
def _transform_if(self, node: If) -> Node | None
Parameters
Name Type Description
node
Returns
Node | None
_transform_with 1 Node
Propagate static values through {% with %} block bindings. Evaluates each bind…
def _transform_with(self, node: With) -> Node

Propagate static values through {% with %} block bindings.

Evaluates each binding expression against the static context. Resolved bindings are added to a sub-evaluator's context so that the body can fold expressions referencing them.

Parameters
Name Type Description
node
Returns
Node
_transform_match 1 Node | None
Eliminate dead match/case branches when subject is compile-time-known. When th…
def _transform_match(self, node: Match) -> Node | None

Eliminate dead match/case branches when subject is compile-time-known.

When the subject resolves, iterate cases and match:

  • Const patterns: exact equality check
  • Name("_") wildcard: always matches
  • Other patterns: bail (leave Match intact)

If subject is unresolved, recurse into each case body.

Parameters
Name Type Description
node
Returns
Node | None
_make_sub_evaluator 1 PartialEvaluator
Create a sub-evaluator with a merged context.
def _make_sub_evaluator(self, ctx: dict[str, Any]) -> PartialEvaluator
Parameters
Name Type Description
ctx
Returns
PartialEvaluator
_transform_assignment 1 Node
Track Set/Let bindings so downstream expressions resolve. When the assigned va…
def _transform_assignment(self, node: Set | Let) -> Node

Track Set/Let bindings so downstream expressions resolve.

When the assigned value can be fully evaluated from the static context, add it to self._ctx so subsequent expressions referencing this variable are also foldable. The value expression is also replaced with a Const so the runtime assignment doesn't fail looking up now-unnecessary vars.

Parameters
Name Type Description
node
Returns
Node
_transform_export 1 Node
Partially transform Export value expression. Export has the same structure as …
def _transform_export(self, node: Export) -> Node

Partially transform Export value expression.

Export has the same structure as Let (name + value), but with export-from-scope semantics. We need to partially transform the value expression so loop variable references are replaced with constants when the enclosing for-loop is unrolled.

Parameters
Name Type Description
node
Returns
Node
_transform_capture 1 Node
Recurse into Capture body so expressions are partially transformed. Without th…
def _transform_capture(self, node: Capture) -> Node

Recurse into Capture body so expressions are partially transformed.

Without this, a Capture inside an unrolled for-loop would retain references to the (now-removed) loop variable.

Parameters
Name Type Description
node
Returns
Node

Functions

partial_evaluate 6 Template
Convenience function: partially evaluate a template AST.
def partial_evaluate(template: Template, static_context: dict[str, Any], *, escape_func: Any | None = None, pure_filters: frozenset[str] = frozenset(), filter_callables: dict[str, Callable[..., Any]] | None = None, inline_components: bool = False) -> Template
Parameters
Name Type Description
template Template

Parsed template AST.

static_context dict[str, Any]

Values known at compile time.

escape_func Any | None

HTML escape function for static Output nodes.

Default:None
pure_filters frozenset[str]

Filter names safe for compile-time evaluation.

Default:frozenset()
filter_callables dict[str, Callable[..., Any]] | None

Filter name to callable for Filter/Pipeline eval.

Default:None
inline_components bool

When True, small {% def %} calls with all-constant arguments are expanded inline at compile time.

Default:False
Returns
Template