ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
preprocessor.generator_mixin.GeneratorMixin Class Reference
Inheritance diagram for preprocessor.generator_mixin.GeneratorMixin:
[legend]

Classes

class  _YieldFromToFor
class  _YieldToAppend
class  _YieldReplacer

Public Member Functions

 visit_GeneratorExp (self, node)

Public Attributes

 comprehension_targets
 module_name
 generator_emitted_init
 early_return_generator_funcs
 dict_items_vars

Protected Member Functions

 _resolve_list_literal_iterable (self, iterable_expr)
 _rename_shadowing_targets (self, node)
 _hoist_shadowed_iter (self, gen_iter, bound_names, node)
 _lower_listcomp (self, node)
 _isolate_genexp_targets (self, genexp_node)
 _prepare_genexp (self, genexp_node)
 _combine_comprehension_ifs (self, ifs)
 _build_reduction_guard (self, tmp_name, source_node, negated)
 _build_genexp_for_body (self, generator, loop_body, guard, source_node)
 _finalize_lowered_genexp (self, for_stmt, source_node)
 _lower_reduction_genexp (self, genexp_node, reduction_stmt, state)
 _lower_any_genexp (self, genexp_node)
 _lower_all_genexp (self, genexp_node)
 _build_param_assigns (self, param_names, call_args, parent_node)
 _apply_yield_replacer (self, body_stmts, replacer_spec, func_name)
 _lower_list_gen_call (self, gen_call_node, parent_node)
 _has_early_return_before_yield (self, body)
 _transform_recursive_generator (self, node)
 _inline_generator_for (self, node)
 _inline_generator_call_for (self, node)
 _find_generator_next_call (self, node)
 _scan_generator_consumers (self, node)
 _consumers_in_order (self, var, scan, gen_defs)
 _collect_loop_yields (self, stmt, yields)
 _make_ternary_yield_value (self, stmt, if_val, else_val)
 _absorb_if_only_yields (self, if_init, if_yields, post, yields)
 _collect_if_yields (self, stmts, i, accum, in_loop)
 _collect_yields (self, stmts, in_loop=False)
 _make_stop_iteration_raise (self, template_node)
 _resolve_next_yield (self, gen_var, func_name)
 _inline_next_call (self, targets, func_name, gen_var, template_node)
 _lower_listcomp_in_expr (self, expr)
 _lower_sorted_with_key_call (self, call_node)
 _fold_dict_getitem_key (self, iterable, key_node)
 _const_element_value (self, node)
 _lower_min_max_with_key_call (self, call_node)
 _scan_unobservable_builtin_keys (cls, module_node)
 _scan_closed_lambda_bindings (cls, module_node)
 _resolve_lambda_key_name (self, key_kw)
 _const_scalar_list_literal (self, literal)
 _sorted_scan_key_supported (self, key_value, literal)
 _scan_key_argument (self, call_node, func_names)
 _key_indexes_element (self, key_value)
 _is_scan_supported (self, iterable_expr, key_value)
 _lower_sorted_key_iterable (self, call_node)
 _lower_sorted_key_scan (self, call_node)
 _lower_min_max_key_scan (self, call_node)
 _eval_const_key_values (self, key_node, elts)
 _eval_const_key_value (self, key_node, element)
 _eval_key_body (self, node, param, element)
 _const_dict_entry (self, node, param, element)
 _lower_tuple_sorted_pair_call (self, call_node)
 _maybe_rewrite_list_sort_with_key (self, expr_node)
 _lower_assert_eq_literal (self, test_node, source_node)
 _try_lower_expr_tuple_literal_eq (self, expr_side, tuple_side, source_node)
 _is_assert_literal_shape (self, node)
 _resolve_known_literal_expr (self, node)
 _is_pure_assert_expr (self, node)
 _build_assert_literal_checks (self, actual_expr, literal_node, source_node)
 _hoist_generator_inits (self, body, template_node)
 _get_dict_expr_from_items_call (self, call_node)
 _get_items_dict_expr (self, node, wrappers)
 _get_dict_view_call (self, node, attr)
 _try_transform_items_set_eq (self, set_side, literal_side, source_node)
 _try_transform_keys_view_eq (self, view_side, literal_side, source_node)
 _try_transform_values_view_eq (self, view_side, literal_side, source_node)
 _try_transform_items_list_eq (self, list_side, literal_side, source_node)
 _try_transform_list_tuple_eq (self, left_side, literal_side, source_node)

Static Protected Member Functions

 _select_min_max_index (key_values, is_min)
 _unguard_starred_call (call)
 _is_recursive_call (func_name, body)
 _recursive_calls (func_name, body)
 _has_yield (node)
 _collect_post (stmts, start)
 _use_in_order (first, last, ctx, kind, only_loop)
 _dict_getitem_key_receiver (iterable, key_node)
 _pack_min_max_key_args (call_node)
 _is_scannable_key (key_value)
 _scan_key_call (key_value, bind_name, arg_expr)
 _inlinable_key_lambda (key_value)
 _binds_key (key_value)
 _inline_key_lambda (key_value, arg_expr)
 _single_key_keyword (call_node, allowed=())
 _keyword_value (call_node, name)
 _sorted_reverse (call_node)
 _is_bool_constant (node)
 _literal_contains_tuple (literal)
 _param_subscript_index (node, param)
 _apply_builtin (name, value)
 _apply_arith (op, left, right)
 _const_scalar_value (node)
 _all_constants_distinct (elts)
 _is_sorted_const_list (elts, *, by="self")

Protected Attributes

dict _gen_next_blacklist
 _lower_sorted_with_key_call
 _lower_sorted_key_scan
 _single_return_funcs
 _known_literal_values

Static Protected Attributes

dict _BUILTIN_KEY_DUNDERS = {"len": "__len__", "abs": "__abs__"}

Member Function Documentation

◆ _absorb_if_only_yields()

preprocessor.generator_mixin.GeneratorMixin._absorb_if_only_yields ( self,
if_init,
if_yields,
post,
yields )
protected
Splice if-branch yields into the parent yields list, including outer post.

◆ _all_constants_distinct()

preprocessor.generator_mixin.GeneratorMixin._all_constants_distinct ( elts)
staticprotected
Return True iff all elts are ast.Constant with statically distinct hashable values.

◆ _apply_arith()

preprocessor.generator_mixin.GeneratorMixin._apply_arith ( op,
left,
right )
staticprotected

◆ _apply_builtin()

preprocessor.generator_mixin.GeneratorMixin._apply_builtin ( name,
value )
staticprotected

◆ _apply_yield_replacer()

preprocessor.generator_mixin.GeneratorMixin._apply_yield_replacer ( self,
body_stmts,
replacer_spec,
func_name )
protected
Inline a generator body, replacing ``yield val`` via ``_YieldReplacer``.

``replacer_spec`` is ``(target_name, body_dest, template_node)``.

Returns the transformed statement list, or ``None`` if the body uses a
construct ``_YieldReplacer`` cannot rewrite.

◆ _binds_key()

preprocessor.generator_mixin.GeneratorMixin._binds_key ( key_value)
staticprotected
Whether a scan binds its key lambda to a name before applying it.

◆ _build_assert_literal_checks()

preprocessor.generator_mixin.GeneratorMixin._build_assert_literal_checks ( self,
actual_expr,
literal_node,
source_node )
protected

◆ _build_genexp_for_body()

preprocessor.generator_mixin.GeneratorMixin._build_genexp_for_body ( self,
generator,
loop_body,
guard,
source_node )
protected

◆ _build_param_assigns()

preprocessor.generator_mixin.GeneratorMixin._build_param_assigns ( self,
param_names,
call_args,
parent_node )
protected
Build ``param = arg`` assignments used when inlining generator calls.

◆ _build_reduction_guard()

preprocessor.generator_mixin.GeneratorMixin._build_reduction_guard ( self,
tmp_name,
source_node,
negated )
protected

◆ _collect_if_yields()

preprocessor.generator_mixin.GeneratorMixin._collect_if_yields ( self,
stmts,
i,
accum,
in_loop )
protected
Process an ``If``. Returns the next index if yields were absorbed, else ``None``.

``accum`` is ``(current_pre, yields)``.

◆ _collect_loop_yields()

preprocessor.generator_mixin.GeneratorMixin._collect_loop_yields ( self,
stmt,
yields )
protected
Process a ``While``/``For`` body. Append yields and return ``True`` if any.

◆ _collect_post()

preprocessor.generator_mixin.GeneratorMixin._collect_post ( stmts,
start )
staticprotected
Collect stmts[start:] up to (not including) the first yield-containing statement.

◆ _collect_yields()

preprocessor.generator_mixin.GeneratorMixin._collect_yields ( self,
stmts,
in_loop = False )
protected
Collect yield points from a generator body.

Returns (outer_init, yields) where:
  outer_init : top-level statements before the first yield/loop-with-yield
               (generator initialisation -- emitted once per generator var).
  yields     : list of (pre_stmts, yield_val, post_stmts, is_repeating)
    pre_stmts : statements inside the innermost scope before this yield.
                For while-loop yields the first item is a guard:
                `if not (loop_cond): raise StopIteration`
    yield_val : the yielded expression (may be an IfExp ternary for if/else)
    post_stmts: statements after this yield until the next yield
                (e.g. `i += 1` after `yield i`)
    is_repeating: True when the yield is inside a loop

◆ _combine_comprehension_ifs()

preprocessor.generator_mixin.GeneratorMixin._combine_comprehension_ifs ( self,
ifs )
protected
Combine a generator's `if` clauses into one condition.

Python allows several `if` clauses in a single comprehension generator
(`[x for x in A if c1 if c2]`), which is equivalent to conjoining them
(`if c1 and c2`). Return the visited single condition, or a BoolOp(And)
over all of them.

◆ _const_dict_entry()

preprocessor.generator_mixin.GeneratorMixin._const_dict_entry ( self,
node,
param,
element )
protected
Constant value of ``param["k"]`` over a dict literal of constants, else None.

◆ _const_element_value()

preprocessor.generator_mixin.GeneratorMixin._const_element_value ( self,
node )
protected
Constant value of a scalar or tuple-of-scalars element, else None.

◆ _const_scalar_list_literal()

preprocessor.generator_mixin.GeneratorMixin._const_scalar_list_literal ( self,
literal )
protected

◆ _const_scalar_value()

preprocessor.generator_mixin.GeneratorMixin._const_scalar_value ( node)
staticprotected
Return the constant value of a literal `node`, handling unary +/-
over a numeric constant (Python parses ``-5`` as UnaryOp(USub, 5)), or
None when the node is not a constant literal.

◆ _consumers_in_order()

preprocessor.generator_mixin.GeneratorMixin._consumers_in_order ( self,
var,
scan,
gen_defs )
protected

◆ _dict_getitem_key_receiver()

preprocessor.generator_mixin.GeneratorMixin._dict_getitem_key_receiver ( iterable,
key_node )
staticprotected
Name ``d`` when the call is ``sorted(d | d.keys(), key=d.__getitem__)``.

◆ _eval_const_key_value()

preprocessor.generator_mixin.GeneratorMixin._eval_const_key_value ( self,
key_node,
element )
protected
Evaluate a constant-foldable ``key=`` argument over one constant list
element, returning the key's Python value or None when not foldable.

``element`` is the element's AST node (a constant scalar, a unary +/-
over one, or a tuple of such constants). ``key_node`` is either a
one-parameter ``lambda`` whose body is built from the parameter, numeric
constants, unary +/-, binary arithmetic, a constant subscript ``p[K]``,
and the ``abs``/``len`` builtins; or the bare ``abs``/``len`` builtin
name. None means "leave the key unapplied" (the caller's existing
fallback), so a value of None is never treated as a foldable key.

◆ _eval_const_key_values()

preprocessor.generator_mixin.GeneratorMixin._eval_const_key_values ( self,
key_node,
elts )
protected
Return the list of constant key values for `key=` applied to each
element, or None when the key form or elements are not constant-
foldable. See ``_eval_const_key_value`` for the supported key forms.

◆ _eval_key_body()

preprocessor.generator_mixin.GeneratorMixin._eval_key_body ( self,
node,
param,
element )
protected
Evaluate a lambda key body for the parameter bound to ``element``.

◆ _finalize_lowered_genexp()

preprocessor.generator_mixin.GeneratorMixin._finalize_lowered_genexp ( self,
for_stmt,
source_node )
protected

◆ _find_generator_next_call()

preprocessor.generator_mixin.GeneratorMixin._find_generator_next_call ( self,
node )
protected
Return (gen_var, func_name) if node contains next(g) for a tracked generator, else None.

◆ _fold_dict_getitem_key()

preprocessor.generator_mixin.GeneratorMixin._fold_dict_getitem_key ( self,
iterable,
key_node )
protected
For ``sorted(d, key=d.__getitem__)`` (or ``d.keys()``) over a tracked
constant dict literal, return ``(key_nodes, value_list)`` so the keys
are ordered by their values; None when the pattern does not apply.

◆ _get_dict_expr_from_items_call()

preprocessor.generator_mixin.GeneratorMixin._get_dict_expr_from_items_call ( self,
call_node )
protected
If call_node is d.items() on a known dict, return the dict expression. Else None.

◆ _get_dict_view_call()

preprocessor.generator_mixin.GeneratorMixin._get_dict_view_call ( self,
node,
attr )
protected
If node is W(d.<attr>()) where W in {list, sorted, set}, return (dict_expr, wrapper).

Returns (None, None) on no match. The wrapper name lets the caller
enforce wrapper-vs-literal-type compatibility (e.g. set wrapper
requires a Set literal). ``sorted(list(...))`` is peeled to
``sorted(...)`` since list() is identity on a view.

◆ _get_items_dict_expr()

preprocessor.generator_mixin.GeneratorMixin._get_items_dict_expr ( self,
node,
wrappers )
protected
Return (dict_expr, wrapper) if node is W(X) where W in `wrappers` and X is a dict_items source.

Returns (None, None) on no match. The wrapper name lets the caller
apply soundness checks that depend on which wrapper is in use
(list/sorted/set differ in ordering semantics). ``sorted(list(...))``
folds to ``sorted(...)`` (list() is identity on a view) so the
cascade matches both idioms.

◆ _has_early_return_before_yield()

preprocessor.generator_mixin.GeneratorMixin._has_early_return_before_yield ( self,
body )
protected
Return True if body has a Return statement before any Yield (linear top-level scan).

◆ _has_yield()

preprocessor.generator_mixin.GeneratorMixin._has_yield ( node)
staticprotected
Return True if node contains a Yield or YieldFrom expression.

◆ _hoist_generator_inits()

preprocessor.generator_mixin.GeneratorMixin._hoist_generator_inits ( self,
body,
template_node )
protected
Scan a loop body for direct `var = next(gen_var)` assignments.
For each normal generator whose outer_init hasn't been emitted yet,
deep-copy the outer_init statements and return them (to be placed
before the loop), and mark the generator as initialized so that
_inline_next_call won't re-emit them inside the loop body.

◆ _hoist_shadowed_iter()

preprocessor.generator_mixin.GeneratorMixin._hoist_shadowed_iter ( self,
gen_iter,
bound_names,
node )
protected
Evaluate a leftmost iterable that the comprehension itself shadows.

Python evaluates the leftmost iterable in the enclosing scope, before
the comprehension binds its target. Leaving it in the generated `for`
evaluated it after, so `[node for node in node.xs]` read `node.xs` off
an element instead of the outer object.

◆ _inlinable_key_lambda()

preprocessor.generator_mixin.GeneratorMixin._inlinable_key_lambda ( key_value)
staticprotected
A ``lambda x: body`` a scan applies by substituting its argument, a
pure read, for ``x``. Bound and called instead, its parameter is typed
before the scan's temporaries exist and defaults to double (#7745).

◆ _inline_generator_call_for()

preprocessor.generator_mixin.GeneratorMixin._inline_generator_call_for ( self,
node )
protected
Inline a for loop over a direct generator function call.

Transforms:
    for x in gen_func(a, b):
        body

Into the generator body with each `yield val` replaced by:
    param_0 = a
    param_1 = b
    x = val
    body

Returns the list of inlined statements, or None if inlining is not
possible (unknown generator, keyword args, arg count mismatch,
non-simple loop target, or generator body contains return/yield-from).

◆ _inline_generator_for()

preprocessor.generator_mixin.GeneratorMixin._inline_generator_for ( self,
node )
protected
Inline a generator-based for loop.

Transforms:
    for x in g:       # where g = gen_func()
        body

Into the generator body with each `yield val` replaced by:
    x = val
    body

Returns the list of inlined statements, or None if inlining is not possible.

◆ _inline_key_lambda()

preprocessor.generator_mixin.GeneratorMixin._inline_key_lambda ( key_value,
arg_expr )
staticprotected
The body of @p key_value with its parameter replaced by @p arg_expr.

◆ _inline_next_call()

preprocessor.generator_mixin.GeneratorMixin._inline_next_call ( self,
targets,
func_name,
gen_var,
template_node )
protected
Inline `x = next(g)` for a normal generator.

Emits outer_init (generator initialisation) on the first call for
gen_var, then per-call: pre_stmts + assignment + post_stmts.
For yields inside loops (is_repeating=True) the index is not advanced.
Pass targets=None for a standalone next(g) with no assignment target.
Returns list of statements, or None if inlining is not possible.

◆ _is_assert_literal_shape()

preprocessor.generator_mixin.GeneratorMixin._is_assert_literal_shape ( self,
node )
protected

◆ _is_bool_constant()

preprocessor.generator_mixin.GeneratorMixin._is_bool_constant ( node)
staticprotected

◆ _is_pure_assert_expr()

preprocessor.generator_mixin.GeneratorMixin._is_pure_assert_expr ( self,
node )
protected

◆ _is_recursive_call()

preprocessor.generator_mixin.GeneratorMixin._is_recursive_call ( func_name,
body )
staticprotected
Return True if any Call in *body* references func_name directly.

Skips nested FunctionDef/AsyncFunctionDef/Lambda bodies: a call to
func_name from inside a nested helper belongs to that helper's scope,
not the generator's, so it must not be treated as a recursive call
(otherwise an unrelated nested helper that happens to reference the
outer name triggers a false-positive recursive-generator transform).

◆ _is_scan_supported()

preprocessor.generator_mixin.GeneratorMixin._is_scan_supported ( self,
iterable_expr,
key_value )
protected
True when a sort scan lowers to operations the frontend models.

The scan copies the iterable with a full slice and applies the key to
each element, and two shapes exceed what the frontend models: copying a
dict view does not terminate at the default unwind, and an element the
key subscripts raises a spurious IndexError. Declining leaves
reject_unfoldable_key to report the unsupported key, which beats a hang
or a wrong verdict.

◆ _is_scannable_key()

preprocessor.generator_mixin.GeneratorMixin._is_scannable_key ( key_value)
staticprotected
True for the key shapes a scan lowering can emit.

A single-parameter lambda is inlined, or bound to a temporary and called
when _inlinable_key_lambda declines it; a plain name is called directly (binding one yields a symbol the callee
resolution does not accept). Of the bound methods only ``__getitem__``
qualifies, because it is emitted as a subscript -- a scan's own
statements are not re-visited, so any other would be a call nothing gets
the chance to rewrite.

◆ _is_sorted_const_list()

preprocessor.generator_mixin.GeneratorMixin._is_sorted_const_list ( elts,
* ,
by = "self" )
staticprotected
Return True iff elts is statically known to be sorted (ascending).

by="self": each elt is ast.Constant, sort by elt.value.
by="first": each elt is ast.Tuple of Constants, sort by first element.

Returns False if any elt is non-Constant or has incomparable types
(conservative — bail when sortedness cannot be statically verified).

◆ _isolate_genexp_targets()

preprocessor.generator_mixin.GeneratorMixin._isolate_genexp_targets ( self,
genexp_node )
protected

◆ _key_indexes_element()

preprocessor.generator_mixin.GeneratorMixin._key_indexes_element ( self,
key_value )
protected
True when applying ``key_value`` subscripts the element it is given.

A key bound by assignment rather than by a def reads as not indexing,
so such a call is still lowered to a scan.

◆ _keyword_value()

preprocessor.generator_mixin.GeneratorMixin._keyword_value ( call_node,
name )
staticprotected

◆ _literal_contains_tuple()

preprocessor.generator_mixin.GeneratorMixin._literal_contains_tuple ( literal)
staticprotected

◆ _lower_all_genexp()

preprocessor.generator_mixin.GeneratorMixin._lower_all_genexp ( self,
genexp_node )
protected
Lower all(elt for target in iter [if cond]) to prefix stmts + boolean result.

Transforms:
    all(elt for target in iter if cond)
Into:
    ESBMC_all_N = True
    for target in iter:
        if cond:          # only when ifs are present
            if ESBMC_all_N:
                if not elt:
                    ESBMC_all_N = False
    <result: ESBMC_all_N>

Uses guard-based short-circuit (checking ESBMC_all_N before evaluating)
instead of break to avoid ESBMC's break+empty-list type inference issue.

◆ _lower_any_genexp()

preprocessor.generator_mixin.GeneratorMixin._lower_any_genexp ( self,
genexp_node )
protected
Lower any(elt for target in iter [if cond]) to prefix stmts + boolean result.

Transforms:
    any(elt for target in iter if cond)
Into:
    ESBMC_any_N = False
    for target in iter:
        if cond:          # only when ifs are present
            if elt:
                ESBMC_any_N = True
    <result: ESBMC_any_N>

◆ _lower_assert_eq_literal()

preprocessor.generator_mixin.GeneratorMixin._lower_assert_eq_literal ( self,
test_node,
source_node )
protected

◆ _lower_list_gen_call()

preprocessor.generator_mixin.GeneratorMixin._lower_list_gen_call ( self,
gen_call_node,
parent_node )
protected
Lower list(gen_func(args...)) to an inline list construction.

Transforms list(gen_func(a, b)) into:
    param_0 = a
    param_1 = b
    ESBMC_list_gen_N: list = []
    # generator body with yield val -> ESBMC_list_gen_N.append(val)

Returns (prefix_stmts, result_name_expr), or (None, gen_call_node) if
inlining is not possible (body has return/yield-from, keyword args, or
positional-arg count mismatch).

◆ _lower_listcomp()

preprocessor.generator_mixin.GeneratorMixin._lower_listcomp ( self,
node )
protected
Lower a list comprehension into prefix statements and result expression.

A comprehension with multiple `for` clauses is not a nested comprehension:
it is semantically equivalent to nested for-loops:
    [f(i,j) for i in A for j in B]  =>  for i in A: for j in B: tmp.append(f(i,j))

◆ _lower_listcomp_in_expr()

preprocessor.generator_mixin.GeneratorMixin._lower_listcomp_in_expr ( self,
expr )
protected
Lower all list comprehensions inside an expression node.

Returns (prefix_stmts, new_expr, result_type) where result_type
is inferred from the transformed root expression.

◆ _lower_min_max_key_scan()

preprocessor.generator_mixin.GeneratorMixin._lower_min_max_key_scan ( self,
call_node )
protected
Lower ``min``/``max(iterable, key=f)`` to an explicit linear scan.

Only reached once ``_lower_min_max_with_key_call`` has declined, i.e. the
iterable or the key is not constant-foldable. The runtime model takes no
key parameter, so without this the key is dropped and the call answers by
natural order. Returns ``(prefix_statements, expr)``, or None when the
shape does not apply.

Ties keep the first occurrence, matching CPython. An empty iterable
raises IndexError here where CPython raises ValueError.

◆ _lower_min_max_with_key_call()

preprocessor.generator_mixin.GeneratorMixin._lower_min_max_with_key_call ( self,
call_node )
protected
Lower min/max(iterable, key=...) for literal-list iterables with a
constant-foldable key.

Two key forms are supported over a list literal whose relevant values
are constants: a one-arg ``lambda x: x[K]`` (constant index) over a
list of tuples, and the ``abs``/``len`` builtins over a list of
constant scalars/strings. The key is evaluated at preprocess time and
the winning *element* (not its key) is returned, breaking ties toward
the first occurrence (CPython semantics). Returns (prefix, expr) on
success, or None when the pattern does not apply (caller falls back to
the regular dispatch, which today drops the key= keyword).

◆ _lower_reduction_genexp()

preprocessor.generator_mixin.GeneratorMixin._lower_reduction_genexp ( self,
genexp_node,
reduction_stmt,
state )
protected

◆ _lower_sorted_key_iterable()

preprocessor.generator_mixin.GeneratorMixin._lower_sorted_key_iterable ( self,
call_node )
protected
Lower a key'd ``sorted()`` standing where an iterable is expected.

Applies the same order as the expression rewriter -- constant fold
first, scan for what it could not fold -- on behalf of the loop
lowering, which builds its iterable assignment after that rewriter has
already run.

◆ _lower_sorted_key_scan()

preprocessor.generator_mixin.GeneratorMixin._lower_sorted_key_scan ( self,
call_node )
protected
Lower ``sorted(iterable, key=f)`` to an explicit insertion sort.

Only reached once ``_lower_sorted_with_key_call`` has declined, i.e. the
iterable or the key is not constant-foldable. The runtime sort model
takes no key parameter, so without this the key is dropped and the call
answers by natural order. Returns ``(prefix_statements, expr)``, or None
when the shape does not apply.

Insertion sort with a strict ``>`` is stable, as CPython's sort is. The
key is applied once per element, in order, into a parallel list the sort
moves with the elements: CPython calls it exactly that way, and an impure
key re-applied in the shift loop was observable. A builtin key whose call
has no side effect is re-applied instead, since the second list costs the
solver several times the sort itself (humaneval_149). The working list is
copied with a full slice rather than ``list(iterable)``, which aliases
its argument -- sorting through the alias would mutate the caller's
list, and ``sorted`` must not.

◆ _lower_sorted_with_key_call()

preprocessor.generator_mixin.GeneratorMixin._lower_sorted_with_key_call ( self,
call_node )
protected
Lower ``sorted(iterable, key=...)`` for constant list-literal iterables.

Supports any constant-foldable ``key=`` (see ``_eval_const_key_value``):
identity ``lambda x: x``, unary ``-x``/``+x``, binary arithmetic over the
parameter and constants, a constant subscript ``lambda x: x[K]`` over a
tuple element, and the ``abs``/``len`` builtins. The key is evaluated at
preprocess time on each constant element and the original element nodes
are reordered accordingly (a stable sort, matching CPython); the elements
themselves are preserved, so their runtime values are unchanged. Returns
``(prefix, expr)`` on success, or None when the iterable or key is not
constant-foldable (the caller then leaves ``key=`` unapplied, as before).

◆ _lower_tuple_sorted_pair_call()

preprocessor.generator_mixin.GeneratorMixin._lower_tuple_sorted_pair_call ( self,
call_node )
protected
Lower tuple(sorted([a, b])) to a conditional pair assignment.

Instead of ``(a, b) if a <= b else (b, a)`` (which ESBMC encodes as a
pointer to a temporary struct — a known crash pattern), we emit:

    _lo = a if a <= b else b
    _hi = b if a <= b else a
    (_lo, _hi)

The result is a 2-tuple whose elements are plain scalar variables.
ESBMC can handle named-scalar tuple construction without the
pointer-to-temporary issue.

◆ _make_stop_iteration_raise()

preprocessor.generator_mixin.GeneratorMixin._make_stop_iteration_raise ( self,
template_node )
protected
Build `raise StopIteration('StopIteration')` AST node.

◆ _make_ternary_yield_value()

preprocessor.generator_mixin.GeneratorMixin._make_ternary_yield_value ( self,
stmt,
if_val,
else_val )
protected
Combine if/else yield values into a single ternary expression.

◆ _maybe_rewrite_list_sort_with_key()

preprocessor.generator_mixin.GeneratorMixin._maybe_rewrite_list_sort_with_key ( self,
expr_node )
protected
If expr_node is `name.sort(key=lambda ...)` (with optional reverse=),
rewrite to `name = sorted(name, key=..., reverse=...)`. Returns the
replacement Assign, or None when the pattern does not apply.

◆ _pack_min_max_key_args()

preprocessor.generator_mixin.GeneratorMixin._pack_min_max_key_args ( call_node)
staticprotected
``min``/``max(a, b, ..., key=f)`` as ``min``/``max([a, b, ...], key=f)``,
which CPython defines to be the same, so the key lowerings see their
one-iterable form. A call argument is left alone: the element type of
a list literal holding it cannot be inferred when the callee has no
return annotation.

◆ _param_subscript_index()

preprocessor.generator_mixin.GeneratorMixin._param_subscript_index ( node,
param )
staticprotected
Return the constant non-negative index of ``param[K]``, or None when
``node`` is not a constant subscript on the lambda parameter ``param``.

◆ _prepare_genexp()

preprocessor.generator_mixin.GeneratorMixin._prepare_genexp ( self,
genexp_node )
protected

◆ _recursive_calls()

preprocessor.generator_mixin.GeneratorMixin._recursive_calls ( func_name,
body )
staticprotected
Yield (call, parent) for each direct call to func_name in *body*.

◆ _rename_shadowing_targets()

preprocessor.generator_mixin.GeneratorMixin._rename_shadowing_targets ( self,
node )
protected
Rename a comprehension target that shadows a known outer variable
or the target of an earlier comprehension.

Python 3 scopes a comprehension's target to the comprehension, so the
rename is semantically free. Without it the outer and inner bindings
share one symbol, and an outer object rebound to elements aborts the
converter in member2t; two comprehensions over elements of different
types give that symbol two types and crash the solver (#9126).

◆ _resolve_known_literal_expr()

preprocessor.generator_mixin.GeneratorMixin._resolve_known_literal_expr ( self,
node )
protected

◆ _resolve_lambda_key_name()

preprocessor.generator_mixin.GeneratorMixin._resolve_lambda_key_name ( self,
key_kw )
protected
``key=k`` for a name _scan_closed_lambda_bindings recorded, as the
lambda itself: called through the name, the scan's call resolves ``k``
as a function symbol and aborts conversion.

◆ _resolve_list_literal_iterable()

preprocessor.generator_mixin.GeneratorMixin._resolve_list_literal_iterable ( self,
iterable_expr )
protected

◆ _resolve_next_yield()

preprocessor.generator_mixin.GeneratorMixin._resolve_next_yield ( self,
gen_var,
func_name )
protected
Look up the next yield to inline for ``gen_var``.

Returns ``(outer_init, entry)`` where ``entry`` is one of:
  * ``None``       -- generator unknown or yields nothing (cannot inline)
  * ``"stop"``     -- generator is exhausted; caller should raise StopIteration
  * tuple          -- ``(pre_stmts, yield_val, post_stmts, is_repeating)``

◆ _scan_closed_lambda_bindings()

preprocessor.generator_mixin.GeneratorMixin._scan_closed_lambda_bindings ( cls,
module_node )
protected
Names the module binds exactly once, to a one-parameter lambda whose
body reads nothing but that parameter: inlining such a body wherever the
name is used cannot capture a different variable.

◆ _scan_generator_consumers()

preprocessor.generator_mixin.GeneratorMixin._scan_generator_consumers ( self,
node )
protected
Blacklist generator variables whose next() calls cannot be inlined.

_inline_next_call picks each next(g)'s yield by its place in the
source, emits the generator's setup at the first next(g), and pastes
the generator's statements into the caller. That matches execution
only when:

  * g is bound once, in straight-line code, and consumed in that scope;
  * every next(g) is the whole value of an assignment or a statement;
  * each next(g) runs in source order: straight-line code, or the last
    one inside a try body. A generator whose only yield sits in a loop
    gives the same value at every call, so after the first call any
    position works, and the first may be an assignment directly in a
    straight-line loop, whose setup _hoist_generator_inits emits;
  * no name the generator binds is used by the caller, nor (in a
    function) any name it reads bound by the caller.

A blacklisted next(g) is left alone, so it stays unsupported.

◆ _scan_key_argument()

preprocessor.generator_mixin.GeneratorMixin._scan_key_argument ( self,
call_node,
func_names )
protected
Return the ``key=`` keyword a scan lowering should apply, else None.

Rejects anything the scans cannot lower: a call that is not
``<name>(iterable, key=...)``, a second keyword, a multi-parameter
lambda, and a key that is not a lambda, a plain name or a bound method
(see _lower_min_max_key_scan for why). An all-constant list
literal is rejected too: the constant fold above owns that case and
declined only because the key values are mutually incomparable, which
the regular dispatch reports as a mixed-element-type error -- scanning
instead would bury that behind a vaguer one.

◆ _scan_key_call()

preprocessor.generator_mixin.GeneratorMixin._scan_key_call ( key_value,
bind_name,
arg_expr )
staticprotected
Build one application of a scan lowering's ``key=``.

``d.__getitem__`` becomes a subscript rather than a call: the statements
a lowering emits are not re-visited, so the dunder-to-operator rewrite in
visit_Call would never reach it and the frontend would raise
AttributeError on the method spelling.

◆ _scan_unobservable_builtin_keys()

preprocessor.generator_mixin.GeneratorMixin._scan_unobservable_builtin_keys ( cls,
module_node )
protected
Builtin keys a scan may re-apply: unbound in the module, and no class
it can see defines their dunder, so a call has no side effect. A module
importing anything but ``typing`` may receive such a class, and is
excluded outright.

◆ _select_min_max_index()

preprocessor.generator_mixin.GeneratorMixin._select_min_max_index ( key_values,
is_min )
staticprotected

◆ _single_key_keyword()

preprocessor.generator_mixin.GeneratorMixin._single_key_keyword ( call_node,
allowed = () )
staticprotected
The call's one ``key=`` keyword, None when it has none or a keyword
outside ``key`` and ``allowed``.

◆ _sorted_reverse()

preprocessor.generator_mixin.GeneratorMixin._sorted_reverse ( call_node)
staticprotected
The ``reverse=`` value of a ``sorted`` call, ``False`` when absent.

◆ _sorted_scan_key_supported()

preprocessor.generator_mixin.GeneratorMixin._sorted_scan_key_supported ( self,
key_value,
literal )
protected

◆ _transform_recursive_generator()

preprocessor.generator_mixin.GeneratorMixin._transform_recursive_generator ( self,
node )
protected
Transform a recursive generator function to accumulate-and-return.

Converts:
    def f(args):
        ...
        yield val
        ...

Into:
    def f(args) -> list:
        ESBMC_gen_result: list = []
        ...
        ESBMC_gen_result.append(val)
        ...
        return ESBMC_gen_result

◆ _try_lower_expr_tuple_literal_eq()

preprocessor.generator_mixin.GeneratorMixin._try_lower_expr_tuple_literal_eq ( self,
expr_side,
tuple_side,
source_node )
protected
Lower ``expr == (c0, c1, ...)`` where *expr* is not a bare Name.

Instead of a struct-to-struct equality (which requires identical Z3
sorts and fails when the function-return struct type differs from the
literal tuple struct type), we **unpack** the tuple and compare each
element individually:

    _u0, _u1, ... = expr
    assert _u0 == c0 and _u1 == c1 and ...

Tuple unpacking is implemented in ESBMC via struct member access
(``element_0``, ``element_1``, ...), so each unpacked variable carries
the correct element type (e.g. ``double_floatbv``).  Element-wise scalar
comparisons then avoid the struct-sort mismatch entirely.

Returns ``(prefix_stmts, new_test)`` when the pattern matches, or
``(None, None)`` when it does not.  Only applies when *tuple_side* is a
tuple literal whose elements are all ``_is_assert_literal_shape`` values
and *expr_side* is not already a ``Name``.

◆ _try_transform_items_list_eq()

preprocessor.generator_mixin.GeneratorMixin._try_transform_items_list_eq ( self,
list_side,
literal_side,
source_node )
protected
Transform list/sorted(d.items()) == [(k,v),...] into dict membership checks.

Rewrites to ``len(d) == N and k_i in d and d[k_i] == v_i`` per pair.
Soundness guards:
- pair keys must be statically distinct Constants. Dict keys are
  distinct, so any literal pair list with duplicate keys never
  equals a real dict's items view; without this guard, e.g.
  ``[(1,'a'),(1,'a')]`` would rewrite to a satisfiable formula
  (``{1:'a', 2:anything}``) — unsound.
- sorted wrapper: literal must be statically sorted by first tuple
  element (CPython sorts tuples by key first; an unsorted literal
  would compare False but the rewrite would say True).
- list wrapper: literal must have at most one pair (ESBMC's dict
  model does not preserve insertion order).

◆ _try_transform_items_set_eq()

preprocessor.generator_mixin.GeneratorMixin._try_transform_items_set_eq ( self,
set_side,
literal_side,
source_node )
protected
Transform set(d.items()) == {(k,v),...} into dict membership checks.

Rewrites to: set(d.keys()) == {k,...} and d[k1] == v1 and d[k2] == v2 ...
This avoids tuple struct comparison and uses only proven-working primitives.
Returns the new AST node, or None if the pattern doesn't match.

◆ _try_transform_keys_view_eq()

preprocessor.generator_mixin.GeneratorMixin._try_transform_keys_view_eq ( self,
view_side,
literal_side,
source_node )
protected
Transform W(d.keys()) == literal into membership checks, where W is one of
list/sorted/set.

Rewrites to ``len(d) == N and k1 in d and k2 in d ...`` — set-equality
semantics. Soundness guards (each bails when violated):
- wrapper-vs-literal type: set ↔ ast.Set; list/sorted ↔ ast.List
  (CPython makes ``list == set`` always False otherwise).
- literal keys must be statically distinct Constants. Dict keys are
  distinct, so any literal with duplicate keys mismatches CPython
  semantics: for set wrapper, ``{1,1}`` dedupes to ``{1}`` (length
  mismatch); for list/sorted, ``[1,1]`` never equals any view of a
  real dict. Bailing here is sound.
- sorted wrapper: literal must be statically sorted ascending
  (otherwise CPython is always False but the rewrite would say True).
- list wrapper: literal must have at most one element
  (ESBMC's dict model does not preserve insertion order).

◆ _try_transform_list_tuple_eq()

preprocessor.generator_mixin.GeneratorMixin._try_transform_list_tuple_eq ( self,
left_side,
literal_side,
source_node )
protected
Transform x == [(a,b), ...] into len/index comparisons for x.

◆ _try_transform_values_view_eq()

preprocessor.generator_mixin.GeneratorMixin._try_transform_values_view_eq ( self,
view_side,
literal_side,
source_node )
protected
Transform list/sorted(d.values()) == [literal] into membership checks.

Rewrites to ``len(d) == N and v1 in d.values() and ...``. Soundness
guards (each bails when violated):
- set wrapper is rejected entirely: dict values may repeat, so the
  rewrite cannot soundly relate ``len(d)`` to ``len(literal_set)``.
- literal must be ast.List of distinct Constants (duplicates would
  collapse to fewer ``in`` checks but pass the length test, turning
  a False assertion into True).
- sorted wrapper: literal must be statically sorted ascending.
- list wrapper: literal must have at most one element (insertion
  order is not modelled).

◆ _unguard_starred_call()

preprocessor.generator_mixin.GeneratorMixin._unguard_starred_call ( call)
staticprotected
The one argument of `call`, with a statement raising the arity
TypeError when it is the guard _lower_starred_call_argument spells
`f(a, *xs)` as: then `f(xs[0], ..., xs[k-1])` and the statement.
list(gen(*xs)) is inlined as statements, so the guard moves ahead of
them (#8670).

◆ _use_in_order()

preprocessor.generator_mixin.GeneratorMixin._use_in_order ( first,
last,
ctx,
kind,
only_loop )
staticprotected

◆ visit_GeneratorExp()

preprocessor.generator_mixin.GeneratorMixin.visit_GeneratorExp ( self,
node )
A generator expression reaches the converter unlowered, so rename its
shadowing target here rather than in the comprehension lowering.

Member Data Documentation

◆ _BUILTIN_KEY_DUNDERS

dict preprocessor.generator_mixin.GeneratorMixin._BUILTIN_KEY_DUNDERS = {"len": "__len__", "abs": "__abs__"}
staticprotected

◆ _gen_next_blacklist

dict preprocessor.generator_mixin.GeneratorMixin._gen_next_blacklist
protected

◆ _known_literal_values

preprocessor.generator_mixin.GeneratorMixin._known_literal_values
protected

◆ _lower_sorted_key_scan

preprocessor.generator_mixin.GeneratorMixin._lower_sorted_key_scan
protected

◆ _lower_sorted_with_key_call

preprocessor.generator_mixin.GeneratorMixin._lower_sorted_with_key_call
protected

◆ _single_return_funcs

preprocessor.generator_mixin.GeneratorMixin._single_return_funcs
protected

◆ comprehension_targets

preprocessor.generator_mixin.GeneratorMixin.comprehension_targets

◆ dict_items_vars

preprocessor.generator_mixin.GeneratorMixin.dict_items_vars

◆ early_return_generator_funcs

preprocessor.generator_mixin.GeneratorMixin.early_return_generator_funcs

◆ generator_emitted_init

preprocessor.generator_mixin.GeneratorMixin.generator_emitted_init

◆ module_name

preprocessor.generator_mixin.GeneratorMixin.module_name

The documentation for this class was generated from the following file: