From cd2e916f8e895ec9f07d5d2be96e38afadc7e1b7 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:44:19 -0500 Subject: [PATCH 01/53] Add provider runtime type regression --- tests/cases/468_provider_type_runtime.py | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/cases/468_provider_type_runtime.py diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py new file mode 100644 index 000000000..92fefcc5a --- /dev/null +++ b/tests/cases/468_provider_type_runtime.py @@ -0,0 +1,32 @@ +# expect: +# 1 +# 0 +# SomniaProvider +# 1 + + +class Provider: + runtime_realms = ("server", "client") + + @classmethod + def supports_realm(cls, realm: str) -> bool: + return realm in cls.runtime_realms + + +class ServerProvider(Provider): + runtime_realms = ("server",) + + +class ClientProvider(Provider): + runtime_realms = ("client",) + + +def starts_with_somnia(value) -> bool: + return value.startswith("somnia.") + + +provider_types = (ServerProvider, ClientProvider) +print(provider_types[0].supports_realm("server")) +print(provider_types[1].supports_realm("server")) +print(str(Provider)) +print(starts_with_somnia("somnia.Scene")) From e3a9a0ed3ec609e5d1fd523e0d54e807de8716be Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:46:12 +0000 Subject: [PATCH 02/53] Record provider runtime compiler inspection --- provider-runtime-inspection.txt | 2008 +++++++++++++++++++++++++++++++ 1 file changed, 2008 insertions(+) create mode 100644 provider-runtime-inspection.txt diff --git a/provider-runtime-inspection.txt b/provider-runtime-inspection.txt new file mode 100644 index 000000000..b4e6d33a2 --- /dev/null +++ b/provider-runtime-inspection.txt @@ -0,0 +1,2008 @@ +=== resolve param types === +3365- for g in group: +3366- r = self._resolve_annot(g.ret_type) +3367- sigs.append(FuncSig( +3368- name=g.name, +3369- arity=len(g.params), +3370- n_defaults=_count_defaults(g.defaults), +3371- pos=g.pos, +3372- ret_type=(r[0], r[1], r[2]) if r is not None else None, +3373- param_names=list(g.params), +3374- param_defaults=list(g.defaults), +3375: param_types=self._resolve_param_types(g), +3376- vararg=g.vararg, +3377- kwarg=g.kwarg, +3378- decorators=list(getattr(g, "decorators", [])), +3379- )) +3380- self._check_overload_group_distinct(name, sigs, group[0].pos) +3381- self.overload_sets[name] = sigs +3382- # Rename each real FuncDef to its mangled symbol IN PLACE -- +3383- # both codegen backends compile a function under its own +3384- # `.name` attribute directly, so this is what actually +3385- # makes each overload land at a distinct symbol. Sema-side +3386- # dispatch (above) reads the mangled name back via +3387- # _overload_symbol(name, sig) applied to the ORIGINAL name + +3388- # the matched FuncSig, so it must produce the identical +3389- # string this rename uses -- both derive it from the same +3390- # (original name, sig) pair via the one shared helper. +3391- for g, sig in zip(group, sigs): +3392- g.name = _overload_symbol(name, sig) +3393- _overload_funcdef_ids.add(id(g)) +3394- +3395- # First pass: collect function signatures so forward references resolve. +3396- for f in self.mod.funcs: +3397- if id(f) in _overload_funcdef_ids: +3398- # Handled by the pre-pass above -- register only the FIRST +3399- # occurrence into self.funcs (so plain, non-dispatch-aware +3400- # code paths that read self.funcs[name] directly, e.g. +3401- # simple existence checks, still find *something* real; the +3402- # actual multi-signature dispatch reads overload_sets +3403- # instead, never this single entry, for these names) and +3404- # skip the ordinary redefinition guard for every copy. +3405- if f.name not in self.funcs: +3406- r = self._resolve_annot(f.ret_type) +3407- _raw_ret_base = f.ret_type[0] if f.ret_type else None +3408- self.funcs[f.name] = FuncSig( +3409- name=f.name, +3410- arity=len(f.params), +3411- n_defaults=_count_defaults(f.defaults), +3412- pos=f.pos, +3413- ret_type=(r[0], r[1], r[2]) if r is not None else None, +3414- ret_list_tuple_types=(r[3] if r is not None and r[1] == "tuple" else None), +3415- ret_inner_el_type=(r[4] if r is not None and r[1] in ("list", "dict") else None), +3416- param_names=list(f.params), +3417- param_defaults=list(f.defaults), +3418: param_types=self._resolve_param_types(f), +3419- vararg=f.vararg, +3420- kwarg=f.kwarg, +3421- ret_tuple=(r[3] if r is not None and r[0] == "tuple" else None), +3422- ret_bool=(_raw_ret_base == "bool"), +3423- decorators=list(getattr(f, "decorators", [])), +3424- ) +3425- continue +3426- if f.name in self.funcs: +3427- if getattr(f, "is_lifted", False): +3428- # A nested `def` is lifted to a module-level function keyed +3429- # by its bare name (see parser.py's nested-def handling and +3430- # A.ClosureBind) -- valid Python allows the same nested-def +3431- # name in unrelated enclosing functions/methods (e.g. two +3432- # methods each defining their own `def _do(): ...`), but +3433- # this flat lifting scheme can't yet keep them apart. +3434- raise SemaError( +3435- f"nested function {f.name!r} collides with another " +3436- f"nested/module function of the same name after being " +3437- "lifted to module scope -- give it a distinct name " +3438- "(asmpython does not yet mangle nested-function names " +3439- "by enclosing scope)", +3440- f.pos, +3441- ErrorCode.E_REDEFINED_FUNC, +3442- ) +3443- raise SemaError(f"function {f.name!r} redefined", f.pos, ErrorCode.E_REDEFINED_FUNC) +3444- if f.name in BUILTINS and not getattr(f, "is_stdlib", False): +3445- raise SemaError( +3446- f"cannot redefine builtin {f.name!r}", +3447- f.pos, +3448- ErrorCode.E_BUILTIN_REDEFINED, +3449- ) +3450- r = self._resolve_annot(f.ret_type) # type: ignore +3451- _raw_ret_base = f.ret_type[0] if f.ret_type else None +3452- self.funcs[f.name] = FuncSig( +3453- name=f.name, +3454- arity=len(f.params), +3455- n_defaults=_count_defaults(f.defaults), +3456- pos=f.pos, +3457- ret_type=(r[0], r[1], r[2]) if r is not None else None, +3458- ret_list_tuple_types=(r[3] if r is not None and r[1] == "tuple" else None), +3459- ret_inner_el_type=(r[4] if r is not None and r[1] in ("list", "dict") else None), +3460- param_names=list(f.params), +3461- param_defaults=list(f.defaults), +3462: param_types=self._resolve_param_types(f), +3463- vararg=f.vararg, +3464- kwarg=f.kwarg, +3465- ret_tuple=(r[3] if r is not None and r[0] == "tuple" else None), +3466- ret_bool=(_raw_ret_base == "bool"), +3467- decorators=list(getattr(f, "decorators", [])), +3468- ) +3469- +3470- # A lightweight top-level prepass for tuple-return inference. This +3471- # lets `_scan_tuple_return` recognize module constants like +3472- # `MODE_REG` as ints before the full function-body pass. +3473- # +3474- # Deliberately NOT calling `_check_stmt`/`_check_expr` here (an +3475- # earlier version of this prepass did): those mutate shared AST +3476- # nodes in place as a side effect of real checking (`_bind_args` +3477- # rewrites a Call's `.args` list to fill in defaults/pack +3478- # `**kwargs`, `_clone_default_expr` allocates fresh nodes, etc.), +3479- # and this prepass runs over `self.mod.body` a second time before +3480- # the real `_try_check_block(self.mod.body, ...)` pass below -- +3481- # so any call node with side-effecting args (e.g. a function +3482- # taking `**kwargs`) got double-expanded, corrupting its arg count +3483- # and raising a bogus "takes N argument(s), got N+1" error the +3484- # second (real) time it was checked. This only needs a syntax-only, +3485- # non-mutating read of simple module-level constant assignments, so +3486- # `_literal_arg_type` (already used for the same purpose elsewhere) +3487- # is enough -- it never touches call sites at all. +3488- self._tuple_scan_globals = Scope() +3489- self._tuple_scan_globals.add("__name__", "str") +3490- self._tuple_scan_globals.add("__file__", "str") +3491- self._tuple_scan_globals.add("__builtins__", "any") +3492- for stmt in self.mod.body: +3493- if not isinstance(stmt, A.Assign): +3494- continue +3495- lit = self._literal_arg_type(stmt.value) +3496- if lit is None: +3497- continue +-- +3693- for g in mgroup: +3694- mr0 = self._resolve_annot(g.ret_type) +3695- msigs.append(FuncSig( +3696- name=g.name, +3697- arity=len(g.params), +3698- n_defaults=_count_defaults(g.defaults), +3699- pos=g.pos, +3700- ret_type=(mr0[0], mr0[1], mr0[2]) if mr0 is not None else None, +3701- param_names=list(g.params), +3702- param_defaults=list(g.defaults), +3703: param_types=self._resolve_param_types(g), +3704- vararg=g.vararg, +3705- kwarg=g.kwarg, +3706- decorators=list(getattr(g, "decorators", [])), +3707- )) +3708- self._check_overload_group_distinct(f"{c.name}.{mname}", msigs, mgroup[0].pos) +3709- self.method_overload_sets[(c.name, mname)] = msigs +3710- for g, msig in zip(mgroup, msigs): +3711- g.name = _overload_symbol(mname, msig) +3712- for m in c.methods: +3713- deco: list[str] = getattr(m, "decorators", []) +3714- is_static = "staticmethod" in deco +3715- is_classm = "classmethod" in deco +3716- if ("private" in deco or "protected" in deco) and not self._ext_active("access"): +3717- raise SemaError( +3718- f"@{'private' if 'private' in deco else 'protected'} on " +3719- f"{c.name}.{m.name} is not supported -- asmpython's " +3720- f"compiler-extension system was withdrawn (see " +3721- f"archived/extensions/)", +3722- m.pos, +3723- ErrorCode.E_DECORATOR_WITHOUT_EXTENSION, +3724- ) +3725- if "final" in deco and not self._ext_active("final"): +3726- raise SemaError( +3727- f"@final on {c.name}.{m.name} is not supported -- " +3728- f"asmpython's compiler-extension system was " +3729- f"withdrawn (see archived/extensions/)", +3730- m.pos, +3731- ErrorCode.E_DECORATOR_WITHOUT_EXTENSION, +3732- ) +3733- if "private" in deco: +3734- sig.access[m.name] = "private" +3735- elif "protected" in deco: +3736- sig.access[m.name] = "protected" +3737- if "final" in deco: +3738- sig.final_methods.add(m.name) +-- +4845- self._collect_returns(st_body, acc) +4846- self._collect_returns(st_handler, acc) +4847- st_extra: list = s.extra_handlers +4848- for _types, _bind, hbody in st_extra: +4849- self._collect_returns(hbody, acc) +4850- st_else: list = s.else_body +4851- st_finally: list = s.finally_body +4852- self._collect_returns(st_else, acc) +4853- self._collect_returns(st_finally, acc) +4854- +4855: def _resolve_param_types(self, f) -> list: +4856- """`overload` extension: resolve each parameter's static type from +4857- its annotation, `"any"` when unannotated (dispatch simply treats +4858- an unannotated param as matching anything -- no attempt to reuse +4859- the default/usage-hint inference machinery here, since that's +4860- keyed to a single concrete function body, not a group of +4861- candidate signatures sharing a name).""" +4862- param_types: list = [] +4863- f_param_types: list = getattr(f, "param_types", []) or [] +4864- for i in range(len(f.params)): +4865- annot = f_param_types[i] if i < len(f_param_types) else None +4866- resolved = self._resolve_annot(annot) +4867- param_types.append(resolved[0] if resolved is not None else "any") +4868- return param_types +4869- +4870- def _check_overload_group_distinct(self, name: str, sigs: list, pos) -> None: +4871- """`overload` extension: reject a group of @overload signatures +4872- that are indistinguishable from each other (same arity AND same +4873- param_types) -- dispatch could never pick between them.""" +4874- seen: set = set() +4875- for sig in sigs: +4876- key = (sig.arity, tuple(sig.param_types)) +4877- if key in seen: +4878- raise SemaError( +4879- f"two @overload signatures for {name!r} are " +4880- f"indistinguishable (same parameter count and types)", +4881- pos, +4882- ErrorCode.E_OVERLOAD_INCOMPATIBLE, +4883- ) +4884- seen.add(key) +4885- +4886- def _resolve_overload(self, name: str, sigs: list, args: list, pos, implicit_self: bool = False): +4887- """`overload` extension: pick the best-matching FuncSig from `sigs` +4888- for a call site's `args`. Filters by arity match first (accounting +4889- for defaults, same as ordinary single-signature arity checking), +4890- then scores the arity-matching candidates by how many parameters +351- # `for row in rows: row[i]` recovers the leaf type. +352- ret_inner_el_type: object = None +353- # Parameter names and their default expressions (parallel to params, +354- # including `self` for methods). Used to bind keyword arguments onto +355- # positions at call sites. +356- param_names: list = field(default_factory=list) +357- param_defaults: list = field(default_factory=list) +358- # `overload` extension: resolved per-parameter static types, parallel to +359- # `param_names` ("int"/"str"/"float"/"any" per slot, "any" for an +360- # unannotated/uninferrable parameter). Populated at registration time +361- # from the same annotation resolution every other FuncSig field already +362- # uses -- previously computed and discarded, never stored, since +363- # ordinary (non-overloaded) call resolution only ever needed arity, not +364- # per-parameter types. Needed here because overload dispatch has to +365- # pick the best-matching signature by argument type, not just count. +366: param_types: list = field(default_factory=list) +367- # Name of the `*args` parameter (the trailing list slot), or None. +368- vararg: Optional[str] = None +369- # Name of the `**kwargs` parameter (the trailing dict slot), or None. +370- kwarg: Optional[str] = None +371- # Per-slot kinds when the body returns a tuple (`return a, b`), so a call +372- # site can unpack `x, y = obj.m()`. None when it doesn't return a tuple. +373- ret_tuple: object = None +374- # Decorator identities for methods (["staticmethod"] / ["classmethod"]). +375- decorators: list = field(default_factory=list) +376- # True when every reachable `return` in the body is a bare `return self` +377- # (and at least one exists), and the method has no explicit return-type +378- # annotation. Lets call sites of e.g. `__enter__` (which conventionally +379- # `return self`) infer `instance:` instead of defaulting to +380- # `int`. Mirrors `ret_tuple`'s body-scanning approach. +381- returns_self: bool = False +382- # True when the function has an explicit `-> bool` annotation (so call +383- # sites can render the return value as True/False in print/str/f-string). +384- ret_bool: bool = False +385- +386- +387-@dataclass +388-class ClassSig: +389- """Compile-time information about a class. +390- +391- `methods` maps method name -> FuncSig (where arity counts `self`). +392- Resolution walks `parent` chains until a method is found. +393- """ +394- +395- name: str +396- parent: Optional[str] +-- +525- +526- +527-# `overload` extension: single canonical symbol-mangling scheme, called +528-# from every resolved-overload-call site (sema, and both codegen backends' +529-# call-emission, which read A.Call.resolved_overload_symbol back rather +530-# than re-deriving it) and from the function-definition-emission side +531-# (codegen/ir_lower, for the actual compiled symbol each @overload def +532-# gets). Suffix is arity plus a short type tag per parameter (i=int, +533-# f=float, s=str, a=any) -- enough to disambiguate same-arity overloads +534-# differing only by parameter type, without a full serialized-signature +535-# mangling scheme this wave's 6 real configs don't need. +536-_OVERLOAD_TYPE_TAG = {"int": "i", "float": "f", "str": "s"} +537- +538- +539-def _overload_symbol(name: str, sig) -> str: +540: tags = "".join(_OVERLOAD_TYPE_TAG.get(t, "a") for t in sig.param_types) +541- return f"{name}__ov{sig.arity}{tags}" +542- +543- +544-def _syntactic_reachable_names(mod: A.Module) -> "tuple[set, set]": +545- """Pre-sema call-graph walk: which top-level functions and class methods +546- are reachable from the module's real entry point (`mod.body`)? +547- +548- Deliberately cheaper and less precise than `ir_lower.py`'s +549- `_reachable_callables` -- that one runs AFTER sema and can key off +550- sema-populated fields (`A.expr_type()`'s `.inferred_type`, +551- `resolved_overload_symbol`, `dunder_call_owner`, etc.) to resolve exactly +552- which class a method call dispatches to. This walker runs BEFORE sema +553- (it has to: it's used to decide whether sema may safely skip a broken +554- body), so it only has the plain-string AST fields the parser already +555- populated: `A.Call.func`, `A.MethodCall.method`, bare `A.Name` refs. It +556- can't tell which class a `MethodCall.method` call targets, so it +557- conservatively marks EVERY class method matching that bare name reachable +558- across all classes -- an over-approximation that only ever marks too +559- much, never too little, which is the safe direction for this walker's +560- one job (deciding what's safe to skip if it errors). +561- """ +562- method_defs: dict = {} +563- methods_by_name: dict = {} +564- for cls in mod.classes: +565- for m in cls.methods: +566- method_defs[(cls.name, m.name)] = m +567- methods_by_name.setdefault(m.name, []).append(cls.name) +568- func_defs = {f.name: f for f in mod.funcs} +569- +570- needed_funcs: set = set() +-- +790- # (mlang_objects), consumed by driver.py's link step. Populated by +791- # _inject_mlang_if_needed, consulted by A.MethodCall's `mlang:` +792- # dispatch above. +793- self.mlang_code_funcs: dict[str, dict] = {} +794- self.mlang_objects: "list[tuple[bytes, str]]" = [] +795- self._tuple_scan_globals: Scope = Scope() +796- # name -> per-slot element kinds for functions that return a tuple +797- # (i.e. have a `return a, b` somewhere). Lets `q, r = f()` recover +798- # the per-target types at the call site. Computed in analyze(). +799- self.func_ret_tuple: dict[str, list[str]] = {} +800- # (qualified_name, param_index) -> (ty, el, val, tup) for parameters +801- # with no annotation and no default, inferred from literal-typed +802- # arguments at call sites. `qualified_name` is a function's plain name, +803- # or "ClassName.method_name" for a method. See +804- # `_infer_unannotated_params`. +805: self.inferred_param_types: dict[str, tuple] = {} +806- # func_name -> list of (ty, el_type, val_type) for each free variable, +807- # populated by _prescan_fv_types() before the main analysis loops. +808- self._fv_types: dict = {} +809- +810- def _ensure_synthetic_func(self, fdef: A.FuncDef, ret_ty: str = "int") -> None: +811- if fdef.name not in self.funcs: +812- self.funcs[fdef.name] = FuncSig( +813- name=fdef.name, +814- arity=len(fdef.params), +815- n_defaults=0, +816- pos=fdef.pos, +817- ret_type=(ret_ty, None, None), +818- param_names=list(fdef.params), +819- param_defaults=[None] * len(fdef.params), +820- ) +821- if not any(f.name == fdef.name for f in self.mod.funcs): +822- self.mod.funcs.append(fdef) +823- +824- def _ensure_builtin_value_func(self, name: str, pos: A.SourcePos) -> str: +825- fname = f"__builtin_value_{name}" +826- if fname in self.funcs: +827- return fname +828- a_name = A.Name(name="a", pos=pos) +829- b_name = A.Name(name="b", pos=pos) +830- cmp = A.Compare(ops=["<" if name == "min" else ">"], operands=[a_name, b_name], pos=pos) +831- body = A.IfExp(test=cmp, body=a_name, orelse=b_name, pos=pos) +832- fdef = A.FuncDef( +833- name=fname, +834- params=["a", "b"], +835- body=[A.Return(value=body, pos=pos)], +836- pos=pos, +837: param_types=[("int", None), ("int", None)], +838- ret_type=("int", None), +839- ) +840- self._ensure_synthetic_func(fdef, "int") +841- return fname +842- +843- def _resolve_class_chain(self, name: str) -> list: +844- """[name, parent, grandparent, ...] for a user-defined class.""" +845- out: list[str] = [] +846- cur = name +847- while cur is not None and cur not in out: +848- out.append(cur) +849- cls: ClassSig = self.classes.get(cur) +850- cur = cls.parent if cls is not None else None +851- return out +852- +853- def _common_class_ancestor(self, a: str, b: str) -> "str | None": +854- """Nearest common ancestor of two user-defined classes, e.g. for +855- `WindowsCodegen` and `Freestanding16Codegen` both descending from +856- `Codegen`. Returns None if they share no modeled ancestor (siblings +857- with only an external/unmodeled base, or unrelated classes).""" +858- chain_a = self._resolve_class_chain(a) +859- chain_b = set(self._resolve_class_chain(b)) +860- for cls_name in chain_a: +861- if cls_name in chain_b: +862- return cls_name +863- return None +864- +865- def _has_external_base(self, class_name: str) -> bool: +866- """True if `class_name` or any ancestor inherits from a base that isn't +867- a user-defined class (a builtin like Exception, or a name imported from +-- +1305- if el in ("list", "dict") and elval is not None: +1306- sig.field_inner_value_types[cname] = elval +1307- elif el == "tuple" and tup: +1308- sig.field_value_tuple_types[cname] = tup +1309- elif ty == "dict" and val is not None: +1310- sig.field_el_types[cname] = val +1311- if val in ("list", "dict") and elval is not None: +1312- sig.field_inner_value_types[cname] = elval +1313- elif val == "tuple" and tup: +1314- sig.field_value_tuple_types[cname] = tup +1315- for m in c.methods: +1316- # Each param maps to its resolved annotation tuple +1317- # (ty, el, val, tuple) so a `self.x = param` assignment can carry +1318- # the param's element/value kinds onto the field. +1319- pinfo: dict = {} +1320: m_param_types_x: list = m.param_types +1321- m_defaults_x: list = m.defaults +1322- # Explicit `: list` intermediates: m is opaque to sema (external +1323- # FuncDef), so m.params / m.body read back as opaque "any". +1324- # Without the cast, enumerate(m.params) / _scan_field_assigns(m.body) +1325- # use the wrong codegen path (int/dict ops instead of list ops). +1326- m_params_cf: list = m.params +1327- m_body_cf: list = m.body +1328- for i, p in enumerate(m_params_cf): +1329- if i == 0: +1330- continue # self +1331: annot = m_param_types_x[i] if i < len(m_param_types_x) else None +1332- r = self._resolve_annot(annot) # type: ignore +1333- if r is not None: +1334- pinfo[p] = r +1335- elif i < len(m_defaults_x) and m_defaults_x[i] is not None: +1336- # A `=None` default carries no real type of its own +1337- # (its literal is IntLit(0, is_none=True)) -- same +1338- # fix as codegen's param-type setup: "any" instead +1339- # of trusting expr_type's "int", so a later +1340- # `self.x = param` doesn't mistype `self.x` as int +1341- # for an Optional[X]-style parameter. +1342- dty = "any" if A.is_none_expr(m_defaults_x[i]) else A.expr_type(m_defaults_x[i]) # type: ignore +1343- pinfo[p] = (dty, None, None, None, None) +1344- else: +1345: inferred = self.inferred_param_types.get(f"{c.name}.{m.name}:{i}") +1346- if inferred is not None: +1347- pinfo[p] = inferred +1348- else: +1349- # A field assigned from an unannotated parameter +1350- # with no literal call-site signal is genuinely +1351- # dynamic. Keep it opaque rather than pinning the +1352- # field to the numeric "int" fallback, so later +1353- # `self.field.method()` / `self.field[...]` uses +1354- # remain lenient. +1355- pinfo[p] = ("any", None, None, None, None) +1356- self._scan_field_assigns(m_body_cf, sig, pinfo) +1357- +1358- def _scan_field_assigns(self, stmts: list, sig: ClassSig, pinfo: dict) -> None: +1359- for s in stmts: +1360- if ( +1361- isinstance(s, A.AttrAssign) +1362- and isinstance(s.obj, A.Name) +1363- and s.obj.name == "self" +1364- ): +1365- # An explicit declaration annotation (`self.x: T = ...`) wins — +1366- # it carries element/value kinds the initializer (often `{}`/`[]`) +1367- # can't. Otherwise fall back to the value's static type. +1368- r = self._resolve_annot(getattr(s, "annot", None)) # type: ignore +1369- if r is not None: +1370- # Same fix as elsewhere: subscript reads with an +1371- # explicit `ty: str`, not a tuple-unpack. +1372- ty: str = r[0] +1373- el = r[1] +1374- val = r[2] +1375- tup = r[3] +-- +1699- # same-named method with conflicting argument types, the +1700- # mismatch just falls back to `int` as before -- no new +1701- # miscompile. +1702- sites = [ +1703- mc for mc in calls +1704- if isinstance(mc, A.MethodCall) and mc.method == m.name +1705- ] +1706- self._infer_call_target_params(f"{c.name}.{m.name}", m, sites, start=1) +1707- +1708- def _infer_call_target_params( +1709- self, qualname: str, fn: A.FuncDef, sites: list, start: int +1710- ) -> None: +1711- """Infer types for `fn`'s parameters at index >= `start` (0 for plain +1712- functions, 1 for methods to skip `self`) from `sites` (the `A.Call`/ +1713- `A.MethodCall` nodes invoking it), storing results in +=== type method semantic branch === +8477- e.tuple_elem_types = self._dict_value_tuple_types(e.obj, scope) +8478- elif e.inferred_type == "dict": +8479- e.value_type = self._dict_inner_value_type(e.obj, scope) +8480- elif e.inferred_type == "tuple": +8481- e.tuple_elem_types = self._dict_value_tuple_types(e.obj, scope) +8482- else: +8483- raise SemaError(f"dict has no method {e.method!r}", e.pos, ErrorCode.E_NO_METHOD) +8484- elif obj_t == "str": +8485- self._check_str_method(e, scope) +8486- return +8487- elif obj_t == "int": +8488- if e.method == "to_bytes": +8489- if not (1 <= len(e.args) <= 3): +8490- raise SemaError("int.to_bytes() takes length[, byteorder[, signed]]", e.pos, ErrorCode.E_ARG_COUNT) +8491- if A.expr_type(e.args[0]) not in ("int", "any"): +8492- raise SemaError("int.to_bytes() length must be an int", e.pos, ErrorCode.E_ARG_TYPE) +8493- if len(e.args) >= 2 and A.expr_type(e.args[1]) not in ("str", "any"): +8494- raise SemaError("int.to_bytes() byteorder must be a str", e.pos, ErrorCode.E_ARG_TYPE) +8495- if len(e.args) >= 3 and A.expr_type(e.args[2]) not in ("int", "any"): +8496- raise SemaError("int.to_bytes() signed must be bool/int", e.pos, ErrorCode.E_ARG_TYPE) +8497- e.inferred_type = "list" +8498- e.list_el_type = "int" +8499- return +8500- raise SemaError(f"int has no method {e.method!r}", e.pos, ErrorCode.E_NO_METHOD) +8501- elif ( +8502: obj_t == "type" +8503- or (isinstance(e.obj, A.Name) and e.obj.name == "int") +8504- ): +8505- if isinstance(e.obj, A.Name) and e.obj.name in self.classes: +8506- cls_name = e.obj.name +8507- resolved = self._resolve_method(cls_name, e.method) +8508- if resolved is None: +8509- raise SemaError(f"{cls_name} has no method {e.method!r}", e.pos, ErrorCode.E_NO_METHOD) +8510- sig: FuncSig = resolved[1] +8511- deco: list[str] = getattr(sig, "decorators", []) +8512- if "classmethod" in deco: +8513- expected = sig.arity - 1 # drop implicit cls +8514- elif "staticmethod" in deco: +8515- expected = sig.arity +8516- else: +8517- raise SemaError( +8518- f"{cls_name}.{e.method}() needs an instance " +8519- "(not a @staticmethod or @classmethod)", +8520- e.pos, +8521- ErrorCode.E_METHOD_NEEDS_INSTANCE, +8522- ) +8523- required = expected - sig.n_defaults +8524- if not (required <= len(e.args) <= expected): +8525- raise SemaError( +8526- f"{cls_name}.{e.method}() takes {required}..{expected} " +8527- f"argument(s), got {len(e.args)}", +8528- e.pos, +8529- ErrorCode.E_ARG_COUNT, +8530- ) +8531- if sig.ret_type is not None: +8532- rt7: tuple = sig.ret_type # type: ignore +8533- ty: str = rt7[0] +8534- el = rt7[1] +8535- e.inferred_type = ty +8536- if ty == "list" and el is not None: +8537- e.list_el_type = el +8538- else: +8539- e.inferred_type = "int" +8540- return +8541- if e.method == "from_bytes": +8542- if not (1 <= len(e.args) <= 3): +8543- raise SemaError("int.from_bytes() takes bytes[, byteorder[, signed]]", e.pos, ErrorCode.E_ARG_COUNT) +8544- if A.expr_type(e.args[0]) not in ("list", "any"): +8545- raise SemaError("int.from_bytes() first argument must be bytes/list[int]", e.pos, ErrorCode.E_ARG_TYPE) +8546- if len(e.args) >= 2 and A.expr_type(e.args[1]) not in ("str", "any"): +8547- raise SemaError("int.from_bytes() byteorder must be a str", e.pos, ErrorCode.E_ARG_TYPE) +8548- if len(e.args) >= 3 and A.expr_type(e.args[2]) not in ("int", "any"): +8549- raise SemaError("int.from_bytes() signed must be bool/int", e.pos, ErrorCode.E_ARG_TYPE) +8550- e.inferred_type = "int" +8551- return +8552- raise SemaError(f"{obj_t} has no method {e.method!r}", e.pos, ErrorCode.E_NO_METHOD) +8553- elif obj_t.startswith("super:"): +8554- # super().method(...) — dispatch against the base class. If the +8555- # base is external (e.g. Exception), we can't model it, so the +8556- # call is lenient. +8557- parent = obj_t.split(":", 1)[1] +8558- if parent not in self.classes: +8559- e.inferred_type = "any" +8560- return +8561- resolved = self._resolve_method(parent, e.method) +8562- if resolved is None: +8563- if self._has_external_base(parent): +8564- e.inferred_type = "any" +8565- return +8566- raise SemaError(f"{parent} has no method {e.method!r}", e.pos, ErrorCode.E_NO_METHOD) +8567- sig: FuncSig = resolved[1] +8568- expected = sig.arity - 1 +8569- required = expected - sig.n_defaults +8570- if not (required <= len(e.args) <= expected): +8571- raise SemaError( +8572- f"super().{e.method}() takes {required}..{expected} " +8573- f"argument(s), got {len(e.args)}", +8574- e.pos, +8575- ErrorCode.E_ARG_COUNT, +8576- ) +8577- if sig.ret_type is not None: +8578- rt5: tuple = sig.ret_type # type: ignore +8579- ty: str = rt5[0] +8580- el = rt5[1] +8581- e.inferred_type = ty +8582- if ty == "list" and el is not None: +8583- e.list_el_type = el +8584- if el in ("list", "dict") and sig.ret_inner_el_type: +8585- e.el_value_type = sig.ret_inner_el_type +8586- else: +8587- e.inferred_type = "int" +8588- return +8589- elif obj_t.startswith("instance:"): +8590- class_name = obj_t.split(":", 1)[1] +8591- ov_key = (class_name, e.method) +8592- if ov_key in self.method_overload_sets: +=== str builtin lowering === +asmpython/_compiler/sema.py-9921- t = A.expr_type(e.args[0]) +asmpython/_compiler/sema.py-9922- if t not in ("str", "list", "dict", "tuple", "set", "any", "int") and not t.startswith("instance:"): +asmpython/_compiler/sema.py-9923- # "int" is the default for unannotated vars — accept leniently +asmpython/_compiler/sema.py-9924- raise SemaError( +asmpython/_compiler/sema.py-9925- "len() requires a string, list, dict, tuple, or set", e.pos, +asmpython/_compiler/sema.py-9926- ErrorCode.E_ARG_TYPE, +asmpython/_compiler/sema.py-9927- ) +asmpython/_compiler/sema.py-9928- elif e.func == "int": +asmpython/_compiler/sema.py-9929- t = A.expr_type(e.args[0]) +asmpython/_compiler/sema.py-9930- # An instance may define __int__ — accepted leniently like +asmpython/_compiler/sema.py-9931- # str()'s __str__/__repr__ dispatch; codegen falls back to +asmpython/_compiler/sema.py-9932- # treating the pointer as a raw int if no __int__ exists. +asmpython/_compiler/sema.py-9933- if t not in ("str", "float", "int", "any") and not t.startswith( +asmpython/_compiler/sema.py-9934- "instance:" +asmpython/_compiler/sema.py-9935- ): +asmpython/_compiler/sema.py-9936- raise SemaError("int() requires str / float / int", e.pos, ErrorCode.E_ARG_TYPE) +asmpython/_compiler/sema.py-9937- elif e.func == "float": +asmpython/_compiler/sema.py-9938- t = A.expr_type(e.args[0]) +asmpython/_compiler/sema.py-9939- if t not in ("str", "int", "float", "any"): +asmpython/_compiler/sema.py-9940- raise SemaError("float() requires str / int / float", e.pos, ErrorCode.E_ARG_TYPE) +asmpython/_compiler/sema.py:9941: elif e.func == "str": +asmpython/_compiler/sema.py-9942- t = A.expr_type(e.args[0]) +asmpython/_compiler/sema.py-9943- # int/float/str convert directly; list/tuple/dict/set stringify +asmpython/_compiler/sema.py-9944- # via their repr; an opaque value or an instance (which may define +asmpython/_compiler/sema.py-9945- # __str__/__repr__) is accepted leniently. All yield a str. +asmpython/_compiler/sema.py-9946- if t not in ( +asmpython/_compiler/sema.py-9947- "int", "float", "str", "any", "list", "tuple", "dict", "set" +asmpython/_compiler/sema.py-9948- ) and not t.startswith("instance:"): +asmpython/_compiler/sema.py-9949- raise SemaError( +asmpython/_compiler/sema.py-9950- "str() requires a scalar, container, or object", e.pos, +asmpython/_compiler/sema.py-9951- ErrorCode.E_ARG_TYPE, +asmpython/_compiler/sema.py-9952- ) +asmpython/_compiler/sema.py-9953- return +asmpython/_compiler/sema.py-9954- if e.func in self.overload_sets: +asmpython/_compiler/sema.py-9955- for a in e.args: +asmpython/_compiler/sema.py-9956- self._check_expr(a, scope) +asmpython/_compiler/sema.py-9957- sig = self._resolve_overload(e.func, self.overload_sets[e.func], e.args, e.pos) +asmpython/_compiler/sema.py-9958- e.resolved_overload_symbol = _overload_symbol(e.func, sig) +asmpython/_compiler/sema.py-9959- self._bind_args( +asmpython/_compiler/sema.py-9960- e, sig.param_names, sig.param_defaults, sig.vararg, e.pos, e.func, +asmpython/_compiler/sema.py-9961- kwarg=sig.kwarg, +asmpython/_compiler/sema.py-9962- ) +asmpython/_compiler/sema.py-9963- if sig.ret_type is not None: +asmpython/_compiler/sema.py-9964- ret_tuple_ov: tuple = sig.ret_type # type: ignore +asmpython/_compiler/sema.py-9965- e.inferred_type = ret_tuple_ov[0] +asmpython/_compiler/sema.py-9966- else: +asmpython/_compiler/sema.py-9967- e.inferred_type = "int" +asmpython/_compiler/sema.py-9968- return +asmpython/_compiler/sema.py-9969- if e.func in self.funcs: +asmpython/_compiler/sema.py-9970- sig = self.funcs[e.func] +asmpython/_compiler/sema.py-9971- self._expand_dstar_kwarg(e, sig.param_names, scope) +asmpython/_compiler/sema.py-9972- # Plain positional calls keep the precise arity diagnostics; calls +asmpython/_compiler/sema.py-9973- # with keyword args or to a `*args` function are validated by the +asmpython/_compiler/sema.py-9974- # binder instead. +asmpython/_compiler/sema.py-9975- if sig.vararg is None and sig.kwarg is None and not e.kwargs: +asmpython/_compiler/sema.py-9976- required = sig.arity - sig.n_defaults +asmpython/_compiler/sema.py-9977- if not (required <= len(e.args) <= sig.arity): +asmpython/_compiler/sema.py-9978- if required == sig.arity: +asmpython/_compiler/sema.py-9979- raise SemaError( +asmpython/_compiler/sema.py-9980- f"{e.func}() takes {sig.arity} argument(s), got {len(e.args)}", +asmpython/_compiler/sema.py-9981- e.pos, +asmpython/_compiler/sema.py-9982- ErrorCode.E_ARG_COUNT, +asmpython/_compiler/sema.py-9983- ) +asmpython/_compiler/sema.py-9984- raise SemaError( +asmpython/_compiler/sema.py-9985- f"{e.func}() takes {required}-{sig.arity} arguments, got {len(e.args)}", +asmpython/_compiler/sema.py-9986- e.pos, +-- +asmpython/_compiler/ir_lower.py-7467- # `0` instead of `7`). No reverse case needed -- a real +asmpython/_compiler/ir_lower.py-7468- # Python float literal/expression passed to an int-typed +asmpython/_compiler/ir_lower.py-7469- # parameter isn't valid input this compiler needs to handle +asmpython/_compiler/ir_lower.py-7470- # leniently, unlike this int-into-float direction, which is +asmpython/_compiler/ir_lower.py-7471- # extremely common (any bare int literal argument). +asmpython/_compiler/ir_lower.py-7472- if ( +asmpython/_compiler/ir_lower.py-7473- i < len(fn.arg_types) +asmpython/_compiler/ir_lower.py-7474- and fn.arg_types[i] == "float" +asmpython/_compiler/ir_lower.py-7475- and av.type is not F64 +asmpython/_compiler/ir_lower.py-7476- ): +asmpython/_compiler/ir_lower.py-7477- fv = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-7478- ctx.emit(IRInstr("sitofp", fv, [av])) +asmpython/_compiler/ir_lower.py-7479- av = fv +asmpython/_compiler/ir_lower.py-7480- args.append(av) +asmpython/_compiler/ir_lower.py-7481- res_ty = F64 if fn.ret_type == "float" else I64 +asmpython/_compiler/ir_lower.py-7482- v = ctx.tmp(res_ty) +asmpython/_compiler/ir_lower.py-7483- ctx.emit(IRInstr("call", v, [c_name] + args)) +asmpython/_compiler/ir_lower.py-7484- return v +asmpython/_compiler/ir_lower.py-7485- +asmpython/_compiler/ir_lower.py-7486- if isinstance(e, A.Call): +asmpython/_compiler/ir_lower.py:7487: if e.func == "str" and len(e.args) == 1: +asmpython/_compiler/ir_lower.py-7488- # Delegates to _lower_expr_as_str, the general str-coercion +asmpython/_compiler/ir_lower.py-7489- # helper f-strings/print() already use -- it covers bool/None +asmpython/_compiler/ir_lower.py-7490- # (this hand-rolled version used to fall through to plain +asmpython/_compiler/ir_lower.py-7491- # decimal conversion for True/False, printing "1"/"0") plus +asmpython/_compiler/ir_lower.py-7492- # tuple/list/dict/set, which this call site never supported at +asmpython/_compiler/ir_lower.py-7493- # all. +asmpython/_compiler/ir_lower.py-7494- return _lower_expr_as_str(ctx, e.args[0]) +asmpython/_compiler/ir_lower.py-7495- if e.func == "int" and len(e.args) in (1, 2): +asmpython/_compiler/ir_lower.py-7496- arg = e.args[0] +asmpython/_compiler/ir_lower.py-7497- arg_t = A.expr_type(arg) +asmpython/_compiler/ir_lower.py-7498- if len(e.args) == 2: +asmpython/_compiler/ir_lower.py-7499- str_v = _lower_expr(ctx, arg) +asmpython/_compiler/ir_lower.py-7500- base_v = _lower_expr(ctx, e.args[1]) +asmpython/_compiler/ir_lower.py-7501- out = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-7502- ctx.emit(IRInstr("call", out, ["_abi_str_to_int_base", str_v, base_v])) +asmpython/_compiler/ir_lower.py-7503- return out +asmpython/_compiler/ir_lower.py-7504- if arg_t == "str": +asmpython/_compiler/ir_lower.py-7505- str_v = _lower_expr(ctx, arg) +asmpython/_compiler/ir_lower.py-7506- out = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-7507- ctx.emit(IRInstr("call", out, ["_abi_str_to_int", str_v])) +asmpython/_compiler/ir_lower.py-7508- return out +asmpython/_compiler/ir_lower.py-7509- if arg_t == "float": +asmpython/_compiler/ir_lower.py-7510- float_v = _lower_expr(ctx, arg) +asmpython/_compiler/ir_lower.py-7511- out = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-7512- ctx.emit(IRInstr("fptosi", out, [float_v])) +asmpython/_compiler/ir_lower.py-7513- return out +asmpython/_compiler/ir_lower.py-7514- if arg_t.startswith("instance:"): +asmpython/_compiler/ir_lower.py-7515- owner = _resolve_method_owner(ctx, arg_t.split(":", 1)[1], "__int__") +asmpython/_compiler/ir_lower.py-7516- if owner is not None: +asmpython/_compiler/ir_lower.py-7517- obj_v = _lower_expr(ctx, arg) +asmpython/_compiler/ir_lower.py-7518- out = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-7519- ctx.emit(IRInstr("call", out, [f"{owner}____int__", obj_v])) +asmpython/_compiler/ir_lower.py-7520- return out +asmpython/_compiler/ir_lower.py-7521- return _lower_expr(ctx, arg) +asmpython/_compiler/ir_lower.py-7522- if e.func == "float" and len(e.args) == 1: +asmpython/_compiler/ir_lower.py-7523- arg = e.args[0] +asmpython/_compiler/ir_lower.py-7524- arg_t = A.expr_type(arg) +asmpython/_compiler/ir_lower.py-7525- # float("nan")/float("inf")/float("-inf") emit the bit +asmpython/_compiler/ir_lower.py-7526- # pattern directly rather than relying on strtod (matches +asmpython/_compiler/ir_lower.py-7527- # codegen.py's own special-case, since UCRT's strtod +asmpython/_compiler/ir_lower.py-7528- # historically had "nan"/"inf" parsing quirks). +asmpython/_compiler/ir_lower.py-7529- if isinstance(arg, A.StrLit): +asmpython/_compiler/ir_lower.py-7530- s = arg.value.strip().lower() +asmpython/_compiler/ir_lower.py-7531- if s == "nan": +asmpython/_compiler/ir_lower.py-7532- out = ctx.tmp(F64) +-- +asmpython/_compiler/codegen.py-13678- arg = e.args[0] +asmpython/_compiler/codegen.py-13679- self.gen_expr(arg, info) # rax = ptr +asmpython/_compiler/codegen.py-13680- t: str = A.expr_type(arg) +asmpython/_compiler/codegen.py-13681- if t in ("list", "tuple"): +asmpython/_compiler/codegen.py-13682- # Tuples reuse the list layout, so len lives at LIST_LEN_OFF. +asmpython/_compiler/codegen.py-13683- self.emitf(f"mov rax, [rax+{self.LIST_LEN_OFF}]") +asmpython/_compiler/codegen.py-13684- elif t in ("dict", "set"): +asmpython/_compiler/codegen.py-13685- # Sets are dict-backed; len lives at DICT_LEN_OFF. +asmpython/_compiler/codegen.py-13686- self.emitf(f"mov rax, [rax+{self.DICT_LEN_OFF}]") +asmpython/_compiler/codegen.py-13687- elif t.startswith("instance:"): +asmpython/_compiler/codegen.py-13688- cls_name = t.split(":", 1)[1] +asmpython/_compiler/codegen.py-13689- owner = self._resolve_method_owner(cls_name, "__len__") +asmpython/_compiler/codegen.py-13690- if owner is not None: +asmpython/_compiler/codegen.py-13691- self.emitf(f"mov {self._arg_reg(0)}, rax") +asmpython/_compiler/codegen.py-13692- self.emit_call(self._method_symbol(owner, "__len__")) +asmpython/_compiler/codegen.py-13693- else: +asmpython/_compiler/codegen.py-13694- self.emitf("xor rax, rax") +asmpython/_compiler/codegen.py-13695- else: +asmpython/_compiler/codegen.py-13696- self._emit_strlen() # rax = length (string) +asmpython/_compiler/codegen.py-13697- return +asmpython/_compiler/codegen.py:13698: if e.func == "str": +asmpython/_compiler/codegen.py-13699- arg_t: str = A.expr_type(e.args[0]) +asmpython/_compiler/codegen.py-13700- self.gen_expr(e.args[0], info) +asmpython/_compiler/codegen.py-13701- # int/float conversions land in the shared static itoa buffer; str() +asmpython/_compiler/codegen.py-13702- # results are commonly stored (e.g. `[str(x) for x in xs]`), so copy +asmpython/_compiler/codegen.py-13703- # out to a fresh allocation to avoid every result aliasing the buffer. +asmpython/_compiler/codegen.py-13704- if arg_t in ("list", "tuple", "dict", "set"): +asmpython/_compiler/codegen.py-13705- # str(container) == repr(container) for these built-ins. +asmpython/_compiler/codegen.py-13706- self._emit_container_repr(e.args[0], arg_t) +asmpython/_compiler/codegen.py-13707- elif arg_t == "float": +asmpython/_compiler/codegen.py-13708- # xmm0 has the value; print into our int_to_str buffer via sprintf. +asmpython/_compiler/codegen.py-13709- self._emit_float_to_str() # rax = ptr (shared buffer) +asmpython/_compiler/codegen.py-13710- self.emitf("call _runtime_str_concat_dup") +asmpython/_compiler/codegen.py-13711- elif arg_t == "str": +asmpython/_compiler/codegen.py-13712- pass # already a str ptr in rax +asmpython/_compiler/codegen.py-13713- elif arg_t.startswith("instance:"): +asmpython/_compiler/codegen.py-13714- resolved = self._resolve_str_dunder(arg_t.split(":", 1)[1]) +asmpython/_compiler/codegen.py-13715- if resolved is not None: +asmpython/_compiler/codegen.py-13716- owner, method = resolved +asmpython/_compiler/codegen.py-13717- self.emitf(f"mov {self._arg_reg(0)}, rax") +asmpython/_compiler/codegen.py-13718- self.emit_call(self._method_symbol(owner, method)) +asmpython/_compiler/codegen.py-13719- else: +asmpython/_compiler/codegen.py-13720- self._emit_int_to_str() # rax = ptr to ASCII +asmpython/_compiler/codegen.py-13721- self.emitf("call _runtime_str_concat_dup") +asmpython/_compiler/codegen.py-13722- elif arg_t == "int" and A.is_bool_expr(e.args[0]): +asmpython/_compiler/codegen.py-13723- self._emit_bool_to_str() +asmpython/_compiler/codegen.py-13724- self.emitf("call _runtime_str_concat_dup") +asmpython/_compiler/codegen.py-13725- elif arg_t == "int" and A.is_none_expr(e.args[0]): +asmpython/_compiler/codegen.py-13726- self.emitf( +asmpython/_compiler/codegen.py-13727- "lea rax, [_runtime_none_str]", "call _runtime_str_concat_dup" +asmpython/_compiler/codegen.py-13728- ) +asmpython/_compiler/codegen.py-13729- else: +asmpython/_compiler/codegen.py-13730- self._emit_int_to_str() # rax = ptr to ASCII (shared buffer) +asmpython/_compiler/codegen.py-13731- self.emitf("call _runtime_str_concat_dup") +asmpython/_compiler/codegen.py-13732- return +asmpython/_compiler/codegen.py-13733- if e.func == "int": +asmpython/_compiler/codegen.py-13734- arg_t = A.expr_type(e.args[0]) +asmpython/_compiler/codegen.py-13735- if len(e.args) == 2: +asmpython/_compiler/codegen.py-13736- # int(s, base): parse the string in the given radix via strtoll. +asmpython/_compiler/codegen.py-13737- # base 0 auto-detects 0x / 0o / 0b prefixes (matches CPython). +asmpython/_compiler/codegen.py-13738- base_slot = info.locals_[f"__int_base_{id(e)}"] +asmpython/_compiler/codegen.py-13739- self.gen_expr(e.args[1], info) # rax = base (int) +asmpython/_compiler/codegen.py-13740- self.emitf(f"mov [rbp{base_slot:+d}], rax") +asmpython/_compiler/codegen.py-13741- self.gen_expr(e.args[0], info) # rax = string ptr +asmpython/_compiler/codegen.py-13742- self.emitf(f"mov rbx, [rbp{base_slot:+d}]") +asmpython/_compiler/codegen.py-13743- self._emit_str_to_int_base() # rax = parsed int +=== class ids === +asmpython/_compiler/sema.py-212- "AttributeError", +asmpython/_compiler/sema.py-213- "KeyError", +asmpython/_compiler/sema.py-214- "IndexError", +asmpython/_compiler/sema.py-215- "LookupError", +asmpython/_compiler/sema.py-216- "StopIteration", +asmpython/_compiler/sema.py-217- "ArithmeticError", +asmpython/_compiler/sema.py-218- "ZeroDivisionError", +asmpython/_compiler/sema.py-219- "OverflowError", +asmpython/_compiler/sema.py-220- "AssertionError", +asmpython/_compiler/sema.py-221- "ImportError", +asmpython/_compiler/sema.py-222- "OSError", +asmpython/_compiler/sema.py-223- "IOError", +asmpython/_compiler/sema.py-224- "FileNotFoundError", +asmpython/_compiler/sema.py-225-}) +asmpython/_compiler/sema.py-226- +asmpython/_compiler/sema.py-227-# Builtin scalar/container type names usable as a bare *value* (not just a +asmpython/_compiler/sema.py-228-# call target or an annotation), e.g. `{"type": str}` mimicking argparse's +asmpython/_compiler/sema.py-229-# `add_argument(type=str)` convention. asmpython has no first-class type +asmpython/_compiler/sema.py-230-# objects -- like a user class or builtin exception used as a value, this +asmpython/_compiler/sema.py-231-# loads a stable per-name RTTI id the program never actually inspects (see +asmpython/_compiler/sema.py:232:# codegen.py's BUILTIN_TYPE_IDS and class_ids). +asmpython/_compiler/sema.py-233-BUILTIN_TYPE_NAMES: frozenset[str] = frozenset({ +asmpython/_compiler/sema.py-234- "int", "float", "str", "bool", "list", "dict", "tuple", "set", +asmpython/_compiler/sema.py-235-}) +asmpython/_compiler/sema.py-236- +asmpython/_compiler/sema.py-237- +asmpython/_compiler/sema.py-238-# Interpreter-only `.` calls: features that require a live +asmpython/_compiler/sema.py-239-# Python interpreter (dynamic import / code execution by string) and so cannot +asmpython/_compiler/sema.py-240-# be compiled to native code. These are in the excluded 0.1% of the language. +asmpython/_compiler/sema.py-241-# Rejected with a clear, located message rather than letting them slip through +asmpython/_compiler/sema.py-242-# the module-leniency path and explode in codegen with a raw traceback. +asmpython/_compiler/sema.py-243-INTERPRETER_ONLY_METHODS: frozenset[tuple[str, str]] = frozenset({ +asmpython/_compiler/sema.py-244- ("importlib", "import_module"), +asmpython/_compiler/sema.py-245- ("importlib", "reload"), +asmpython/_compiler/sema.py-246- ("imp", "load_module"), +asmpython/_compiler/sema.py-247-}) +asmpython/_compiler/sema.py-248- +asmpython/_compiler/sema.py-249-# Interpreter-only *builtins* (bare calls, not module methods). +asmpython/_compiler/sema.py-250-INTERPRETER_ONLY_BUILTINS: frozenset[str] = frozenset({ +asmpython/_compiler/sema.py-251- "eval", +asmpython/_compiler/sema.py-252- "exec", +asmpython/_compiler/sema.py-253- "compile", +asmpython/_compiler/sema.py-254- "__import__", +asmpython/_compiler/sema.py-255- "globals", +asmpython/_compiler/sema.py-256- "locals", +asmpython/_compiler/sema.py-257- "vars", +asmpython/_compiler/sema.py-258-}) +asmpython/_compiler/sema.py-259- +asmpython/_compiler/sema.py-260-# Binary operator -> (forward dunder, reflected dunder). A user class can +asmpython/_compiler/sema.py-261-# overload `a b` by defining the forward method on `a`'s class, or the +asmpython/_compiler/sema.py-262-# reflected method on `b`'s class (used when `a`'s type doesn't define the +asmpython/_compiler/sema.py-263-# forward method, mirroring Python's `NotImplemented` fallback — but since +asmpython/_compiler/sema.py-264-# asmpython doesn't model `NotImplemented`, the forward method wins whenever +asmpython/_compiler/sema.py-265-# it exists). E.g. `Path("a") / "b"` resolves `Path.__truediv__`. +asmpython/_compiler/sema.py-266-DUNDER_BINOP: dict[str, tuple[str, str]] = { +asmpython/_compiler/sema.py-267- "+": ("__add__", "__radd__"), +asmpython/_compiler/sema.py-268- "-": ("__sub__", "__rsub__"), +asmpython/_compiler/sema.py-269- "*": ("__mul__", "__rmul__"), +asmpython/_compiler/sema.py-270- "/": ("__truediv__", "__rtruediv__"), +asmpython/_compiler/sema.py-271- "//": ("__floordiv__", "__rfloordiv__"), +asmpython/_compiler/sema.py-272- "%": ("__mod__", "__rmod__"), +-- +asmpython/_compiler/ir_lower.py-134- # to a compile-time-known scalar value (stdlib.Const), NOT a real +asmpython/_compiler/ir_lower.py-135- # runtime global. `_lower_expr`'s A.Name case checks this BEFORE +asmpython/_compiler/ir_lower.py-136- # falling back to the generic slot/global lookup (see there for +asmpython/_compiler/ir_lower.py-137- # why: without this, the name silently became an uninitialized +asmpython/_compiler/ir_lower.py-138- # local defaulting to I64, corrupting anything that read it). +asmpython/_compiler/ir_lower.py-139- self.ffi_consts = ffi_consts or {} +asmpython/_compiler/ir_lower.py-140- self.imported_modules = imported_modules or {} +asmpython/_compiler/ir_lower.py-141- # import_binary()/.imported dynamic-loading (see the "import_binary +asmpython/_compiler/ir_lower.py-142- # dynamic DLL loading" section near the end of this file): handle +asmpython/_compiler/ir_lower.py-143- # variable name -> list of (func_name, FuncDef) for every top-level +asmpython/_compiler/ir_lower.py-144- # `@.imported` stub decorated for it. Built once in +asmpython/_compiler/ir_lower.py-145- # lower_module from the whole-program mod.funcs, mirroring +asmpython/_compiler/ir_lower.py-146- # codegen.py's Codegen.imported_funcs exactly (same dict shape, same +asmpython/_compiler/ir_lower.py-147- # ".imported" decorator-suffix scan) so both backends resolve the +asmpython/_compiler/ir_lower.py-148- # same set of dynamically-imported functions per handle. +asmpython/_compiler/ir_lower.py-149- self.imported_funcs: dict[str, list[tuple[str, "A.FuncDef"]]] = imported_funcs or {} +asmpython/_compiler/ir_lower.py-150- self.classes_sig = classes_sig or {} +asmpython/_compiler/ir_lower.py-151- self.global_types = global_types or {} +asmpython/_compiler/ir_lower.py-152- self.global_names = frozenset(self.global_types) +asmpython/_compiler/ir_lower.py-153- self.global_list_el_ty = global_list_el_ty or {} +asmpython/_compiler/ir_lower.py:154: self.class_ids: dict[str, int] = { +asmpython/_compiler/ir_lower.py-155- name: i for i, name in enumerate(sorted(class_names)) +asmpython/_compiler/ir_lower.py-156- } +asmpython/_compiler/ir_lower.py-157- # `class C: x = 5` (plain class, not @dataclass -- a dataclass's +asmpython/_compiler/ir_lower.py-158- # class vars are per-instance fields, handled entirely differently) +asmpython/_compiler/ir_lower.py-159- # static class-level variables: `ClassName.attr` reads/writes a +asmpython/_compiler/ir_lower.py-160- # real dedicated global, one per (class, var) pair, initialized +asmpython/_compiler/ir_lower.py-161- # from its default expression at module-init time. Mirrors +asmpython/_compiler/ir_lower.py-162- # codegen.py's `class_var_labels`/`class_var_defaults` exactly +asmpython/_compiler/ir_lower.py-163- # (`__cv___` label convention) -- this was previously +asmpython/_compiler/ir_lower.py-164- # entirely unimplemented on this backend: `ClassName.attr` fell +asmpython/_compiler/ir_lower.py-165- # through to the generic instance-attribute dict-lookup fallback, +asmpython/_compiler/ir_lower.py-166- # which treated the class's raw RTTI id (a small integer, e.g. 0) +asmpython/_compiler/ir_lower.py-167- # as if it were a real object pointer and dereferenced it, +asmpython/_compiler/ir_lower.py-168- # crashing (confirmed via gdb: SIGSEGV reading near address 0). +asmpython/_compiler/ir_lower.py-169- self.class_var_labels: dict[tuple[str, str], str] = {} +asmpython/_compiler/ir_lower.py-170- self.class_var_defaults: list[tuple[str, "A.Expr"]] = [] +asmpython/_compiler/ir_lower.py-171- for cls in classes or []: +asmpython/_compiler/ir_lower.py-172- if getattr(cls, "is_dataclass", False): +asmpython/_compiler/ir_lower.py-173- continue +asmpython/_compiler/ir_lower.py-174- for cv in getattr(cls, "class_vars", []) or []: +asmpython/_compiler/ir_lower.py-175- cvname, _annot, cvdefault = cv +asmpython/_compiler/ir_lower.py-176- if cvdefault is None: +asmpython/_compiler/ir_lower.py-177- continue +asmpython/_compiler/ir_lower.py-178- label = f"__cv_{cls.name}__{cvname}" +asmpython/_compiler/ir_lower.py-179- self.class_var_labels[(cls.name, cvname)] = label +asmpython/_compiler/ir_lower.py-180- self.class_var_defaults.append((label, cvdefault)) +asmpython/_compiler/ir_lower.py-181- self._str_names: dict[str, str] = {} +asmpython/_compiler/ir_lower.py-182- self._n = 0 +asmpython/_compiler/ir_lower.py-183- +asmpython/_compiler/ir_lower.py-184- def intern_str(self, value: str) -> str: +asmpython/_compiler/ir_lower.py-185- if value in self._str_names: +asmpython/_compiler/ir_lower.py-186- return self._str_names[value] +asmpython/_compiler/ir_lower.py-187- self._n += 1 +asmpython/_compiler/ir_lower.py-188- name = f"__str_{self._n}" +asmpython/_compiler/ir_lower.py-189- self.data.append(IRGlobal(name=name, type=PTR, value=value)) +asmpython/_compiler/ir_lower.py-190- self._str_names[value] = name +asmpython/_compiler/ir_lower.py-191- return name +asmpython/_compiler/ir_lower.py-192- +asmpython/_compiler/ir_lower.py-193- +asmpython/_compiler/ir_lower.py-194-class _FuncCtx: +-- +asmpython/_compiler/ir_lower.py-2212- return "staticmethod" in getattr(msig, "decorators", []) +asmpython/_compiler/ir_lower.py-2213- +asmpython/_compiler/ir_lower.py-2214- +asmpython/_compiler/ir_lower.py-2215-def _resolve_method_owner(ctx: _FuncCtx, class_name: str, method: str) -> str | None: +asmpython/_compiler/ir_lower.py-2216- for cname in _resolve_class_chain(ctx, class_name): +asmpython/_compiler/ir_lower.py-2217- sig = ctx.mctx.classes_sig.get(cname) +asmpython/_compiler/ir_lower.py-2218- if sig is not None and method in sig.methods: +asmpython/_compiler/ir_lower.py-2219- return cname +asmpython/_compiler/ir_lower.py-2220- return None +asmpython/_compiler/ir_lower.py-2221- +asmpython/_compiler/ir_lower.py-2222- +asmpython/_compiler/ir_lower.py-2223-def _virtual_dispatch_rows(ctx: _FuncCtx, class_name: str, method: str) -> list[tuple[int, str]]: +asmpython/_compiler/ir_lower.py-2224- """[(class_id, owner)] for every user class that is `class_name` or +asmpython/_compiler/ir_lower.py-2225- descends from it and resolves `method` somewhere on its chain -- +asmpython/_compiler/ir_lower.py-2226- mirrors codegen.py's _virtual_dispatch_rows exactly. A method call on a +asmpython/_compiler/ir_lower.py-2227- `class_name`-typed receiver can bind statically only when every row +asmpython/_compiler/ir_lower.py-2228- shares one owner; with overrides in play the call must dispatch on the +asmpython/_compiler/ir_lower.py-2229- instance's runtime __class__ id instead, since the static type names +asmpython/_compiler/ir_lower.py-2230- the base but the receiver at runtime may be a subclass.""" +asmpython/_compiler/ir_lower.py-2231- rows: list[tuple[int, str]] = [] +asmpython/_compiler/ir_lower.py:2232: for cname, cid in ctx.mctx.class_ids.items(): +asmpython/_compiler/ir_lower.py-2233- if class_name not in _resolve_class_chain(ctx, cname): +asmpython/_compiler/ir_lower.py-2234- continue +asmpython/_compiler/ir_lower.py-2235- owner = _resolve_method_owner(ctx, cname, method) +asmpython/_compiler/ir_lower.py-2236- if owner is not None: +asmpython/_compiler/ir_lower.py-2237- rows.append((cid, owner)) +asmpython/_compiler/ir_lower.py-2238- return rows +asmpython/_compiler/ir_lower.py-2239- +asmpython/_compiler/ir_lower.py-2240- +asmpython/_compiler/ir_lower.py:2241:def _subclass_ids(ctx: _FuncCtx, target: str) -> list[int]: +asmpython/_compiler/ir_lower.py-2242- ids: list[int] = [] +asmpython/_compiler/ir_lower.py:2243: for name, cid in ctx.mctx.class_ids.items(): +asmpython/_compiler/ir_lower.py-2244- cur = name +asmpython/_compiler/ir_lower.py-2245- seen: list[str] = [] +asmpython/_compiler/ir_lower.py-2246- while cur and cur not in seen: +asmpython/_compiler/ir_lower.py-2247- if cur == target: +asmpython/_compiler/ir_lower.py-2248- ids.append(cid) +asmpython/_compiler/ir_lower.py-2249- break +asmpython/_compiler/ir_lower.py-2250- seen.append(cur) +asmpython/_compiler/ir_lower.py-2251- sig = ctx.mctx.classes_sig.get(cur) +asmpython/_compiler/ir_lower.py-2252- cur = sig.parent if sig is not None else None +asmpython/_compiler/ir_lower.py-2253- return ids +asmpython/_compiler/ir_lower.py-2254- +asmpython/_compiler/ir_lower.py-2255- +asmpython/_compiler/ir_lower.py-2256-def _resolve_str_dunder(ctx: _FuncCtx, class_name: str, repr_first: bool = False) -> tuple[str, str] | None: +asmpython/_compiler/ir_lower.py-2257- methods = ("__repr__", "__str__") if repr_first else ("__str__", "__repr__") +asmpython/_compiler/ir_lower.py-2258- for method in methods: +asmpython/_compiler/ir_lower.py-2259- owner = _resolve_method_owner(ctx, class_name, method) +asmpython/_compiler/ir_lower.py-2260- if owner is not None: +asmpython/_compiler/ir_lower.py-2261- return owner, method +asmpython/_compiler/ir_lower.py-2262- return None +asmpython/_compiler/ir_lower.py-2263- +asmpython/_compiler/ir_lower.py-2264- +asmpython/_compiler/ir_lower.py-2265-def _value_repr_kind(t: str) -> int: +asmpython/_compiler/ir_lower.py-2266- if t == "str": +asmpython/_compiler/ir_lower.py-2267- return 1 +asmpython/_compiler/ir_lower.py-2268- if t == "float": +asmpython/_compiler/ir_lower.py-2269- return 2 +asmpython/_compiler/ir_lower.py-2270- return 0 +asmpython/_compiler/ir_lower.py-2271- +asmpython/_compiler/ir_lower.py-2272- +asmpython/_compiler/ir_lower.py-2273-def _composite_repr_kind(t: str, inner: str) -> int: +asmpython/_compiler/ir_lower.py-2274- if t == "list": +asmpython/_compiler/ir_lower.py-2275- return 3 | (_value_repr_kind(inner) << 4) +asmpython/_compiler/ir_lower.py-2276- if t == "dict": +asmpython/_compiler/ir_lower.py-2277- return 4 | (_value_repr_kind(inner) << 4) +asmpython/_compiler/ir_lower.py-2278- if t == "tuple": +asmpython/_compiler/ir_lower.py-2279- return 5 +asmpython/_compiler/ir_lower.py-2280- return _value_repr_kind(t) +asmpython/_compiler/ir_lower.py-2281- +asmpython/_compiler/ir_lower.py-2282- +asmpython/_compiler/ir_lower.py-2283-def _list_repr_kind(e: A.Expr) -> int: +-- +asmpython/_compiler/ir_lower.py-3786- arg0_t = A.expr_type(arg0) +asmpython/_compiler/ir_lower.py-3787- has_prim_target = False +asmpython/_compiler/ir_lower.py-3788- prim_match = False +asmpython/_compiler/ir_lower.py-3789- for t in targets: +asmpython/_compiler/ir_lower.py-3790- if t in prim_map: +asmpython/_compiler/ir_lower.py-3791- has_prim_target = True +asmpython/_compiler/ir_lower.py-3792- if arg0_t in prim_map[t]: +asmpython/_compiler/ir_lower.py-3793- prim_match = True +asmpython/_compiler/ir_lower.py-3794- if t == "int" and A.is_bool_expr(arg0): +asmpython/_compiler/ir_lower.py-3795- prim_match = True +asmpython/_compiler/ir_lower.py-3796- if t == "bool" and A.is_bool_expr(arg0): +asmpython/_compiler/ir_lower.py-3797- prim_match = True +asmpython/_compiler/ir_lower.py-3798- if has_prim_target: +asmpython/_compiler/ir_lower.py-3799- _lower_expr(ctx, arg0) +asmpython/_compiler/ir_lower.py-3800- out = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3801- ctx.emit(IRInstr("const", out, [1 if prim_match else 0])) +asmpython/_compiler/ir_lower.py-3802- return out +asmpython/_compiler/ir_lower.py-3803- +asmpython/_compiler/ir_lower.py-3804- accept: list[int] = [] +asmpython/_compiler/ir_lower.py-3805- for t in targets: +asmpython/_compiler/ir_lower.py:3806: for cid in _subclass_ids(ctx, t): +asmpython/_compiler/ir_lower.py-3807- if cid not in accept: +asmpython/_compiler/ir_lower.py-3808- accept.append(cid) +asmpython/_compiler/ir_lower.py-3809- +asmpython/_compiler/ir_lower.py-3810- obj_v = _lower_expr(ctx, arg0) +asmpython/_compiler/ir_lower.py-3811- zero = ctx.tmp(PTR if obj_v.type == PTR else I64) +asmpython/_compiler/ir_lower.py-3812- ctx.emit(IRInstr("const", zero, [0])) +asmpython/_compiler/ir_lower.py-3813- out_ptr = ctx.ensure_slot(f"__isinst_out_{id(e)}", I64) +asmpython/_compiler/ir_lower.py-3814- none_b = ctx.new_block("isinstnone") +asmpython/_compiler/ir_lower.py-3815- live_b = ctx.new_block("isinstlive") +asmpython/_compiler/ir_lower.py-3816- end_b = ctx.new_block("isinstend") +asmpython/_compiler/ir_lower.py-3817- is_none = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3818- ctx.emit(IRInstr("icmp.eq", is_none, [obj_v, zero])) +asmpython/_compiler/ir_lower.py-3819- ctx.emit(IRInstr("br.t", None, [is_none, none_b.label, live_b.label])) +asmpython/_compiler/ir_lower.py-3820- +asmpython/_compiler/ir_lower.py-3821- ctx.switch_to(none_b) +asmpython/_compiler/ir_lower.py-3822- none_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3823- ctx.emit(IRInstr("const", none_v, [0])) +asmpython/_compiler/ir_lower.py-3824- ctx.emit(IRInstr("store", None, [none_v, out_ptr])) +asmpython/_compiler/ir_lower.py-3825- ctx.emit(IRInstr("br", None, [end_b.label])) +asmpython/_compiler/ir_lower.py-3826- +asmpython/_compiler/ir_lower.py-3827- ctx.switch_to(live_b) +asmpython/_compiler/ir_lower.py-3828- key_sym = ctx.mctx.intern_str("__class__") +asmpython/_compiler/ir_lower.py-3829- key_v = ctx.tmp(PTR) +asmpython/_compiler/ir_lower.py-3830- ctx.emit(IRInstr("global_addr", key_v, [key_sym])) +asmpython/_compiler/ir_lower.py-3831- miss_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3832- ctx.emit(IRInstr("const", miss_v, [-1])) +asmpython/_compiler/ir_lower.py-3833- class_id = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3834- ctx.emit(IRInstr("call", class_id, ["_abi_dict_get_default", obj_v, key_v, miss_v])) +asmpython/_compiler/ir_lower.py-3835- match_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3836- ctx.emit(IRInstr("const", match_v, [0])) +asmpython/_compiler/ir_lower.py-3837- for cid in accept: +asmpython/_compiler/ir_lower.py-3838- cid_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3839- ctx.emit(IRInstr("const", cid_v, [cid])) +asmpython/_compiler/ir_lower.py-3840- eq_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3841- ctx.emit(IRInstr("icmp.eq", eq_v, [class_id, cid_v])) +asmpython/_compiler/ir_lower.py-3842- next_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3843- ctx.emit(IRInstr("ior", next_v, [match_v, eq_v])) +asmpython/_compiler/ir_lower.py-3844- match_v = next_v +asmpython/_compiler/ir_lower.py-3845- ctx.emit(IRInstr("store", None, [match_v, out_ptr])) +asmpython/_compiler/ir_lower.py-3846- ctx.emit(IRInstr("br", None, [end_b.label])) +-- +asmpython/_compiler/ir_lower.py-4460- _emit_instance_field_set(ctx, obj_v, "_alive", zero_v) +asmpython/_compiler/ir_lower.py-4461- return obj_v +asmpython/_compiler/ir_lower.py-4462- +asmpython/_compiler/ir_lower.py-4463- +asmpython/_compiler/ir_lower.py-4464-def _lower_expr(ctx: _FuncCtx, e: A.Expr) -> IRValue: +asmpython/_compiler/ir_lower.py-4465- if isinstance(e, A.IntLit): +asmpython/_compiler/ir_lower.py-4466- v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-4467- ctx.emit(IRInstr("const", v, [int(e.value)])) +asmpython/_compiler/ir_lower.py-4468- return v +asmpython/_compiler/ir_lower.py-4469- +asmpython/_compiler/ir_lower.py-4470- if isinstance(e, A.FloatLit): +asmpython/_compiler/ir_lower.py-4471- v = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-4472- ctx.emit(IRInstr("const", v, [float(e.value)])) +asmpython/_compiler/ir_lower.py-4473- return v +asmpython/_compiler/ir_lower.py-4474- +asmpython/_compiler/ir_lower.py-4475- if isinstance(e, A.Name): +asmpython/_compiler/ir_lower.py-4476- if e.name in ctx.mctx.func_names and e.name not in ctx.slot_ty: +asmpython/_compiler/ir_lower.py-4477- v = ctx.tmp(PTR) +asmpython/_compiler/ir_lower.py-4478- ctx.emit(IRInstr("global_addr", v, [e.name])) +asmpython/_compiler/ir_lower.py-4479- return v +asmpython/_compiler/ir_lower.py:4480: if e.name in ctx.mctx.class_ids: +asmpython/_compiler/ir_lower.py-4481- v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py:4482: ctx.emit(IRInstr("const", v, [ctx.mctx.class_ids[e.name]])) +asmpython/_compiler/ir_lower.py-4483- return v +asmpython/_compiler/ir_lower.py-4484- if e.name in BUILTIN_TYPE_IDS: +asmpython/_compiler/ir_lower.py-4485- v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-4486- ctx.emit(IRInstr("const", v, [BUILTIN_TYPE_IDS[e.name]])) +asmpython/_compiler/ir_lower.py-4487- return v +asmpython/_compiler/ir_lower.py-4488- if e.name in ctx.mctx.ffi_consts and e.name not in ctx.slot_ty: +asmpython/_compiler/ir_lower.py-4489- # `from math import pi` -- a bare name bound to a stdlib.Const, +asmpython/_compiler/ir_lower.py-4490- # not a real runtime global at all (the binding table itself +asmpython/_compiler/ir_lower.py-4491- # IS the value, resolved at COMPILE time). Was entirely +asmpython/_compiler/ir_lower.py-4492- # unhandled: fell through to the generic slot/global fallback +asmpython/_compiler/ir_lower.py-4493- # below, which allocated a fresh, never-initialized local +asmpython/_compiler/ir_lower.py-4494- # slot defaulting to I64 and read GARBAGE stack memory as the +asmpython/_compiler/ir_lower.py-4495- # constant's value -- confirmed via a real repro (`from math +asmpython/_compiler/ir_lower.py-4496- # import pi, sqrt; print(int(sqrt(pi * pi)))`) crashing the +asmpython/_compiler/ir_lower.py-4497- # COMPILER itself (not the compiled binary): the garbage +asmpython/_compiler/ir_lower.py-4498- # I64-typed value flowed into `pi * pi`'s `fmul`, and +asmpython/_compiler/ir_lower.py-4499- # regalloc allocated it a GP register to match its wrong +asmpython/_compiler/ir_lower.py-4500- # type, so codegen's XMM-only binop path hit a `RegLoc` +asmpython/_compiler/ir_lower.py-4501- # where it expected a `StackLoc`. Mirrors the existing +asmpython/_compiler/ir_lower.py-4502- # `module.CONST`-style `A.Attr` FFI-const handling elsewhere +asmpython/_compiler/ir_lower.py-4503- # in this file (see its own much longer comment for the +asmpython/_compiler/ir_lower.py-4504- # `value_windows` override rationale) -- same value +asmpython/_compiler/ir_lower.py-4505- # resolution, just for the from-import bare-name spelling +asmpython/_compiler/ir_lower.py-4506- # instead of the module-attribute spelling. `e.name not in +asmpython/_compiler/ir_lower.py-4507- # ctx.slot_ty` guards against a local variable shadowing the +asmpython/_compiler/ir_lower.py-4508- # imported constant's name (rare but real -- a function-local +asmpython/_compiler/ir_lower.py-4509- # `pi = 3` inside code that also imported `math.pi` at module +asmpython/_compiler/ir_lower.py-4510- # scope must read the LOCAL, not the FFI constant). +asmpython/_compiler/ir_lower.py-4511- b = ctx.mctx.ffi_consts[e.name] +asmpython/_compiler/ir_lower.py-4512- value = getattr(b, "value_windows", None) +asmpython/_compiler/ir_lower.py-4513- if value is None: +asmpython/_compiler/ir_lower.py-4514- value = getattr(b, "value", None) +asmpython/_compiler/ir_lower.py-4515- if b.ty == "str" and isinstance(value, str): +asmpython/_compiler/ir_lower.py-4516- name = ctx.mctx.intern_str(value) +asmpython/_compiler/ir_lower.py-4517- v = ctx.tmp(PTR) +asmpython/_compiler/ir_lower.py-4518- ctx.emit(IRInstr("global_addr", v, [name])) +asmpython/_compiler/ir_lower.py-4519- return v +asmpython/_compiler/ir_lower.py-4520- if b.ty == "int" and isinstance(value, (int, bool)): +asmpython/_compiler/ir_lower.py-4521- v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-4522- ctx.emit(IRInstr("const", v, [int(value)])) +-- +asmpython/_compiler/ir_lower.py-7403- ctx.emit(IRInstr("const", zero, [0])) +asmpython/_compiler/ir_lower.py-7404- v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-7405- ctx.emit(IRInstr("call", v, ["_abi_dict_get_default", obj_val, key_ptr, zero])) +asmpython/_compiler/ir_lower.py-7406- if A.expr_type(e) == "float": +asmpython/_compiler/ir_lower.py-7407- # Every dict/instance-attribute cell is a plain 8-byte int slot +asmpython/_compiler/ir_lower.py-7408- # (_abi_dict_set/get_default only ever move GP-sized values); +asmpython/_compiler/ir_lower.py-7409- # a float attribute's bits went in via bitcast_f2i on write +asmpython/_compiler/ir_lower.py-7410- # (see A.AttrAssign below) and must come back out the same way, +asmpython/_compiler/ir_lower.py-7411- # not as a numeric int->float conversion (sitofp would treat +asmpython/_compiler/ir_lower.py-7412- # the raw bit pattern as an integer value, corrupting it). +asmpython/_compiler/ir_lower.py-7413- fv = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-7414- ctx.emit(IRInstr("bitcast_i2f", fv, [v])) +asmpython/_compiler/ir_lower.py-7415- return fv +asmpython/_compiler/ir_lower.py-7416- return v +asmpython/_compiler/ir_lower.py-7417- +asmpython/_compiler/ir_lower.py-7418- if isinstance(e, A.Call) and e.func in ctx.mctx.class_names: +asmpython/_compiler/ir_lower.py-7419- if e.func == "Thread": +asmpython/_compiler/ir_lower.py-7420- return _lower_thread_ctor(ctx, e) +asmpython/_compiler/ir_lower.py-7421- v = ctx.tmp(PTR) +asmpython/_compiler/ir_lower.py-7422- ctx.emit(IRInstr("call", v, ["_abi_new_instance"])) +asmpython/_compiler/ir_lower.py:7423: cid = ctx.mctx.class_ids.get(e.func) +asmpython/_compiler/ir_lower.py-7424- if cid is not None: +asmpython/_compiler/ir_lower.py-7425- key_name = ctx.mctx.intern_str("__class__") +asmpython/_compiler/ir_lower.py-7426- key_v = ctx.tmp(PTR) +asmpython/_compiler/ir_lower.py-7427- cid_v = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-7428- ctx.emit(IRInstr("global_addr", key_v, [key_name])) +asmpython/_compiler/ir_lower.py-7429- ctx.emit(IRInstr("const", cid_v, [cid])) +asmpython/_compiler/ir_lower.py-7430- ctx.emit(IRInstr("call", None, ["_abi_dict_set", v, key_v, cid_v])) +asmpython/_compiler/ir_lower.py-7431- owner = _resolve_method_owner(ctx, e.func, "__init__") +asmpython/_compiler/ir_lower.py-7432- if owner is not None: +asmpython/_compiler/ir_lower.py-7433- init_args = [v] +asmpython/_compiler/ir_lower.py-7434- for arg in e.args: +asmpython/_compiler/ir_lower.py-7435- init_args.append(_lower_expr(ctx, arg)) +asmpython/_compiler/ir_lower.py-7436- ctx.emit(IRInstr("call", None, [f"{owner}____init__", *init_args])) +asmpython/_compiler/ir_lower.py-7437- return v +asmpython/_compiler/ir_lower.py-7438- +asmpython/_compiler/ir_lower.py-7439- if isinstance(e, A.Call) and e.func in ctx.mctx.ffi_funcs: +asmpython/_compiler/ir_lower.py-7440- # A bound stdlib FFI function (e.g. asmlib.hardware.in_byte/cpuid/ +asmpython/_compiler/ir_lower.py-7441- # disable_interrupts, or a real libm export like math.sqrt): call +asmpython/_compiler/ir_lower.py-7442- # its real c_name symbol, not the asmpython-level name. Argument +asmpython/_compiler/ir_lower.py-7443- # marshaling is exactly what a normal "call" IR op already does +asmpython/_compiler/ir_lower.py-7444- # (the same standard-ABI argument passing _gen_ffi_call does by +asmpython/_compiler/ir_lower.py-7445- # hand in the legacy codegen.py for the same bindings) -- but the +asmpython/_compiler/ir_lower.py-7446- # RESULT type must follow the binding's own declared `ret_type`, +asmpython/_compiler/ir_lower.py-7447- # not be hardcoded I64: hardware.py's bindings are genuinely all +asmpython/_compiler/ir_lower.py-7448- # int, but math.py's aren't (sqrt/sin/cos/... return float, in +asmpython/_compiler/ir_lower.py-7449- # XMM0, not RAX). Was hardcoded I64 unconditionally -- confirmed +asmpython/_compiler/ir_lower.py-7450- # via a real repro (`int(sqrt(49))`): the call's result got typed +asmpython/_compiler/ir_lower.py-7451- # I64 despite the real ABI return coming back in XMM0, so the +asmpython/_compiler/ir_lower.py-7452- # immediately-following `fptosi` (which expects an F64 SOURCE +asmpython/_compiler/ir_lower.py-7453- # location) fed it a value the allocator had placed in a GP +asmpython/_compiler/ir_lower.py-7454- # register, tripping codegen's `_dst_xmm`-style location-kind +asmpython/_compiler/ir_lower.py-7455- # assert (`'RegLoc' object has no attribute 'offset'`). +asmpython/_compiler/ir_lower.py-7456- fn = ctx.mctx.ffi_funcs[e.func] +asmpython/_compiler/ir_lower.py-7457- c_name = getattr(fn, "c_name_windows", None) or fn.c_name +asmpython/_compiler/ir_lower.py-7458- args = [] +asmpython/_compiler/ir_lower.py-7459- for i, a in enumerate(e.args): +asmpython/_compiler/ir_lower.py-7460- av = _lower_expr(ctx, a) +asmpython/_compiler/ir_lower.py-7461- # Coerce an int-typed argument to float when the binding +asmpython/_compiler/ir_lower.py-7462- # declares a float parameter (e.g. `sqrt(49)` -- a bare int +asmpython/_compiler/ir_lower.py-7463- # literal into a `("float",)`-typed binding): without this, +-- +asmpython/_compiler/ir_lower.py-9352- def add_resolved(class_name: str, method: str) -> None: +asmpython/_compiler/ir_lower.py-9353- add(_resolve_method_owner_in_sigs(classes_sig, class_name, method), method) +asmpython/_compiler/ir_lower.py-9354- +asmpython/_compiler/ir_lower.py-9355- def add_resolved_virtual(class_name: str, method: str) -> None: +asmpython/_compiler/ir_lower.py-9356- # Walker-side counterpart to ir_lower.py's own `_virtual_dispatch_ +asmpython/_compiler/ir_lower.py-9357- # rows` (used at LOWERING time to emit a runtime __class__-id +asmpython/_compiler/ir_lower.py-9358- # dispatch chain whenever more than one subclass overrides a +asmpython/_compiler/ir_lower.py-9359- # method) -- that function makes lowering correctly CALL every +asmpython/_compiler/ir_lower.py-9360- # subclass override's real symbol, but nothing previously marked +asmpython/_compiler/ir_lower.py-9361- # those overrides reachable for THIS walker, which only ever +asmpython/_compiler/ir_lower.py-9362- # looked upward from the receiver's own static type +asmpython/_compiler/ir_lower.py-9363- # (add_resolved/_resolve_method_owner_in_sigs). Any user class +asmpython/_compiler/ir_lower.py-9364- # that is `class_name` or descends from it and resolves `method` +asmpython/_compiler/ir_lower.py-9365- # anywhere on its own chain needs its owner marked reachable too +asmpython/_compiler/ir_lower.py-9366- # -- not just the statically-resolved owner -- since the +asmpython/_compiler/ir_lower.py-9367- # receiver's runtime __class__ may be any of them. Same +asmpython/_compiler/ir_lower.py-9368- # "lowering fixed, walker not fixed" shape as every other +asmpython/_compiler/ir_lower.py-9369- # dunder/lambda/super() dispatch gap fixed this session; mirrors +asmpython/_compiler/ir_lower.py-9370- # `_virtual_dispatch_rows`'s downward scan but reimplemented +asmpython/_compiler/ir_lower.py-9371- # against the module-level `classes_sig` dict (no live `ctx` / +asmpython/_compiler/ir_lower.py:9372: # `class_ids` exists yet at this walker's point in the pipeline). +asmpython/_compiler/ir_lower.py-9373- add_resolved(class_name, method) +asmpython/_compiler/ir_lower.py-9374- for cname in classes_sig: +asmpython/_compiler/ir_lower.py-9375- seen: set[str] = set() +asmpython/_compiler/ir_lower.py-9376- cur = cname +asmpython/_compiler/ir_lower.py-9377- descends = False +asmpython/_compiler/ir_lower.py-9378- while cur is not None and cur not in seen: +asmpython/_compiler/ir_lower.py-9379- seen.add(cur) +asmpython/_compiler/ir_lower.py-9380- if cur == class_name: +asmpython/_compiler/ir_lower.py-9381- descends = True +asmpython/_compiler/ir_lower.py-9382- break +asmpython/_compiler/ir_lower.py-9383- sig = classes_sig.get(cur) +asmpython/_compiler/ir_lower.py-9384- cur = sig.parent if sig is not None else None +asmpython/_compiler/ir_lower.py-9385- if descends: +asmpython/_compiler/ir_lower.py-9386- add_resolved(cname, method) +asmpython/_compiler/ir_lower.py-9387- +asmpython/_compiler/ir_lower.py-9388- def visit(node) -> None: +asmpython/_compiler/ir_lower.py-9389- if node is None or isinstance(node, (str, int, float, bool)): +asmpython/_compiler/ir_lower.py-9390- return +asmpython/_compiler/ir_lower.py-9391- if isinstance(node, A.MethodCall): +asmpython/_compiler/ir_lower.py-9392- obj_ty = A.expr_type(node.obj) +asmpython/_compiler/ir_lower.py-9393- # `overload` extension: a resolved overload method call's real +asmpython/_compiler/ir_lower.py-9394- # target is its mangled method name (node.method is the +asmpython/_compiler/ir_lower.py-9395- # original, ambiguous, never-actually-emitted bare name -- +asmpython/_compiler/ir_lower.py-9396- # every source-level @overload method was renamed to its own +asmpython/_compiler/ir_lower.py-9397- # mangled name during sema's per-class overload pre-pass). +asmpython/_compiler/ir_lower.py-9398- resolved_ov_m = getattr(node, "resolved_overload_symbol", None) +asmpython/_compiler/ir_lower.py-9399- dispatch_method = resolved_ov_m if resolved_ov_m is not None else node.method +asmpython/_compiler/ir_lower.py-9400- if obj_ty.startswith("instance:"): +asmpython/_compiler/ir_lower.py-9401- add_resolved_virtual(obj_ty.split(":", 1)[1], dispatch_method) +asmpython/_compiler/ir_lower.py-9402- elif obj_ty.startswith("super:"): +asmpython/_compiler/ir_lower.py-9403- # super().method(...): dispatches statically to the base +asmpython/_compiler/ir_lower.py-9404- # class's OWN method (never a subclass override -- see +asmpython/_compiler/ir_lower.py-9405- # ir_lower.py's `super:` MethodCall lowering), so mark +asmpython/_compiler/ir_lower.py-9406- # exactly that resolved owner reachable. Same +asmpython/_compiler/ir_lower.py-9407- # "lowering fixed, walker not fixed" shape as every other +asmpython/_compiler/ir_lower.py-9408- # dunder/lambda dispatch gap this session -- add_resolved +asmpython/_compiler/ir_lower.py-9409- # already handles the None-owner (non-user-class base) +asmpython/_compiler/ir_lower.py-9410- # case as a no-op. +asmpython/_compiler/ir_lower.py-9411- add_resolved(obj_ty.split(":", 1)[1], node.method) +asmpython/_compiler/ir_lower.py-9412- elif obj_ty == "type" and isinstance(node.obj, A.Name): +-- +asmpython/_compiler/codegen.py-68- "ZeroDivisionError": 6, +asmpython/_compiler/codegen.py-69- "OverflowError": 7, +asmpython/_compiler/codegen.py-70- "LookupError": 8, +asmpython/_compiler/codegen.py-71- "IndexError": 9, +asmpython/_compiler/codegen.py-72- "KeyError": 10, +asmpython/_compiler/codegen.py-73- "NameError": 11, +asmpython/_compiler/codegen.py-74- "AttributeError": 12, +asmpython/_compiler/codegen.py-75- "TypeError": 13, +asmpython/_compiler/codegen.py-76- "ValueError": 14, +asmpython/_compiler/codegen.py-77- "RuntimeError": 15, +asmpython/_compiler/codegen.py-78- "NotImplementedError": 16, +asmpython/_compiler/codegen.py-79- "AssertionError": 17, +asmpython/_compiler/codegen.py-80- "ImportError": 18, +asmpython/_compiler/codegen.py-81- "OSError": 19, +asmpython/_compiler/codegen.py-82- "FileNotFoundError": 20, +asmpython/_compiler/codegen.py-83- "StopIteration": 21, +asmpython/_compiler/codegen.py-84- "IOError": 19, # alias for OSError (same id) +asmpython/_compiler/codegen.py-85-} +asmpython/_compiler/codegen.py-86- +asmpython/_compiler/codegen.py-87-# A bare builtin scalar/container type name used as a value (`{"type": str}`, +asmpython/_compiler/codegen.py:88:# mimicking argparse's `type=str`) -- same RTTI-id trick as class_ids / +asmpython/_compiler/codegen.py-89-# BUILTIN_EXC_IDS: asmpython has no first-class type objects, so this is just +asmpython/_compiler/codegen.py-90-# a stable, unique-per-name placeholder the program never actually inspects. +asmpython/_compiler/codegen.py:91:# Negative so it can never collide with class_ids (which starts at 0). +asmpython/_compiler/codegen.py-92-BUILTIN_TYPE_IDS: dict[str, int] = { +asmpython/_compiler/codegen.py-93- "int": -1, +asmpython/_compiler/codegen.py-94- "float": -2, +asmpython/_compiler/codegen.py-95- "str": -3, +asmpython/_compiler/codegen.py-96- "bool": -4, +asmpython/_compiler/codegen.py-97- "list": -5, +asmpython/_compiler/codegen.py-98- "dict": -6, +asmpython/_compiler/codegen.py-99- "tuple": -7, +asmpython/_compiler/codegen.py-100- "set": -8, +asmpython/_compiler/codegen.py-101-} +asmpython/_compiler/codegen.py-102- +asmpython/_compiler/codegen.py-103- +asmpython/_compiler/codegen.py-104-# --- Function metadata -------------------------------------------------------- +asmpython/_compiler/codegen.py-105- +asmpython/_compiler/codegen.py-106- +asmpython/_compiler/codegen.py-107-@dataclass +asmpython/_compiler/codegen.py-108-class FuncInfo: +asmpython/_compiler/codegen.py-109- name: str +asmpython/_compiler/codegen.py-110- params: list[str] +asmpython/_compiler/codegen.py-111- locals_: dict[str, int] = field( +asmpython/_compiler/codegen.py-112- default_factory=dict +asmpython/_compiler/codegen.py-113- ) # name -> RBP offset (negative) +asmpython/_compiler/codegen.py-114- local_types: dict[str, str] = field( +asmpython/_compiler/codegen.py-115- default_factory=dict +asmpython/_compiler/codegen.py-116- ) # name -> 'int'|'float'|'str'|'list' +asmpython/_compiler/codegen.py-117- frame_size: int = 0 # bytes to subtract from RSP +asmpython/_compiler/codegen.py-118- # Default value expressions, one per param (None for required). +asmpython/_compiler/codegen.py-119- defaults: list = field(default_factory=list) +asmpython/_compiler/codegen.py-120- # Running RBP offset (negative) used while collecting locals, and whether +=== MethodCall codegen === +asmpython/_compiler/program.py-331- if name == "asyncio": +asmpython/_compiler/program.py-332- return 1 +asmpython/_compiler/program.py-333- if name == "importlib": +asmpython/_compiler/program.py-334- return 1 +asmpython/_compiler/program.py-335- return 0 +asmpython/_compiler/program.py-336- +asmpython/_compiler/program.py-337- +asmpython/_compiler/program.py-338-def _flatten_targets(targets: list, out: set[str]) -> None: +asmpython/_compiler/program.py-339- """Collect every name bound by a (possibly nested) unpack target list, +asmpython/_compiler/program.py-340- e.g. `["a", ["b", "c"]]` -> {"a", "b", "c"}. Mirrors sema's +asmpython/_compiler/program.py-341- `_flat_target_names` for the subset program.py needs.""" +asmpython/_compiler/program.py-342- for t in targets: +asmpython/_compiler/program.py-343- if isinstance(t, str): +asmpython/_compiler/program.py-344- out.add(t) +asmpython/_compiler/program.py-345- elif isinstance(t, list): +asmpython/_compiler/program.py-346- _flatten_targets(t, out) +asmpython/_compiler/program.py-347- +asmpython/_compiler/program.py-348- +asmpython/_compiler/program.py-349-def _free_names(node: object, out: set[str]) -> None: +asmpython/_compiler/program.py-350- """Collect the bare names an expression references: `Name` lookups and +asmpython/_compiler/program.py:351: `Call`/`MethodCall` callee names. Used to decide whether a value-import's +asmpython/_compiler/program.py-352- initializer can be safely materialized (every name it needs must already +asmpython/_compiler/program.py-353- be available). Attribute names and string literals are not free +asmpython/_compiler/program.py-354- variables, so they're skipped. Explicit per-node-type walk over every +asmpython/_compiler/program.py-355- expression shape (no statement shapes: every call site passes a single +asmpython/_compiler/program.py-356- expression, e.g. an import initializer or an `if`/assert test). +asmpython/_compiler/program.py-357- """ +asmpython/_compiler/program.py-358- if node is None: +asmpython/_compiler/program.py-359- return +asmpython/_compiler/program.py-360- if isinstance(node, A.Name): +asmpython/_compiler/program.py-361- out.add(node.name) +asmpython/_compiler/program.py-362- return +asmpython/_compiler/program.py-363- if isinstance(node, A.Call): +asmpython/_compiler/program.py-364- out.add(node.func) +asmpython/_compiler/program.py-365- for a in node.args: +asmpython/_compiler/program.py-366- _free_names(a, out) +asmpython/_compiler/program.py-367- for _kw, val in node.kwargs: +asmpython/_compiler/program.py-368- _free_names(val, out) +asmpython/_compiler/program.py-369- return +asmpython/_compiler/program.py:370: if isinstance(node, A.MethodCall): +asmpython/_compiler/program.py-371- # `obj.method(...)`: the receiver and args are sub-expressions; the +asmpython/_compiler/program.py-372- # method name itself is an attribute, not a free variable. +asmpython/_compiler/program.py-373- _free_names(node.obj, out) +asmpython/_compiler/program.py-374- for a in node.args: +asmpython/_compiler/program.py-375- _free_names(a, out) +asmpython/_compiler/program.py-376- for _kw, val in node.kwargs: +asmpython/_compiler/program.py-377- _free_names(val, out) +asmpython/_compiler/program.py-378- return +asmpython/_compiler/program.py-379- if isinstance(node, A.Attr): +asmpython/_compiler/program.py-380- # `obj.name`: only the object is a free reference. +asmpython/_compiler/program.py-381- _free_names(node.obj, out) +asmpython/_compiler/program.py-382- return +asmpython/_compiler/program.py-383- if isinstance(node, A.Comprehension): +asmpython/_compiler/program.py-384- # `[elt for a, b in iter if cond]`: `var`/`targets` are loop-bound +asmpython/_compiler/program.py-385- # names, not free references — collect names from the rest of the +asmpython/_compiler/program.py-386- # node (elt/key/value/iter/cond/extra_for_*) and drop the bound +asmpython/_compiler/program.py-387- # ones, so e.g. `{fwd for fwd, _rfl in DUNDER_BINOP.values()}` +asmpython/_compiler/program.py-388- # reports only `DUNDER_BINOP` as free, not `fwd`/`_rfl`. +asmpython/_compiler/program.py-389- _nc: A.Comprehension = node +asmpython/_compiler/program.py-390- bound: set[str] = set() +asmpython/_compiler/program.py-391- if _nc.var: +asmpython/_compiler/program.py-392- bound.add(_nc.var) +asmpython/_compiler/program.py-393- _flatten_targets(_nc.targets, bound) +asmpython/_compiler/program.py-394- for t in _nc.extra_for_vars: +asmpython/_compiler/program.py-395- if t: +asmpython/_compiler/program.py-396- bound.add(t) +asmpython/_compiler/program.py-397- for t in _nc.extra_for_targets: +asmpython/_compiler/program.py-398- _flatten_targets(t, bound) +asmpython/_compiler/program.py-399- inner: set[str] = set() +asmpython/_compiler/program.py-400- _free_names(_nc.elt, inner) +asmpython/_compiler/program.py-401- _free_names(_nc.iter, inner) +asmpython/_compiler/program.py-402- if _nc.cond is not None: +asmpython/_compiler/program.py-403- _free_names(_nc.cond, inner) +asmpython/_compiler/program.py-404- for ei in _nc.extra_for_iters: +asmpython/_compiler/program.py-405- _free_names(ei, inner) +asmpython/_compiler/program.py-406- for ec in _nc.extra_for_conds: +asmpython/_compiler/program.py-407- if ec is not None: +asmpython/_compiler/program.py-408- _free_names(ec, inner) +asmpython/_compiler/program.py-409- out |= inner - bound +asmpython/_compiler/program.py-410- return +asmpython/_compiler/program.py-411- if isinstance(node, A.DictComprehension): +asmpython/_compiler/program.py-412- # Unlike A.Comprehension, DictComprehension has no extra_for_* +asmpython/_compiler/program.py-413- # fields — it only supports a single `for` clause. +asmpython/_compiler/program.py-414- _ndc: A.DictComprehension = node +asmpython/_compiler/program.py-415- bound2: set[str] = set() +asmpython/_compiler/program.py-416- if _ndc.var: +asmpython/_compiler/program.py-417- bound2.add(_ndc.var) +asmpython/_compiler/program.py-418- _flatten_targets(_ndc.targets, bound2) +asmpython/_compiler/program.py-419- inner2: set[str] = set() +asmpython/_compiler/program.py-420- _free_names(_ndc.key, inner2) +asmpython/_compiler/program.py-421- _free_names(_ndc.value, inner2) +asmpython/_compiler/program.py-422- _free_names(_ndc.iter, inner2) +asmpython/_compiler/program.py-423- if _ndc.cond is not None: +asmpython/_compiler/program.py-424- _free_names(_ndc.cond, inner2) +asmpython/_compiler/program.py-425- out |= inner2 - bound2 +asmpython/_compiler/program.py-426- return +asmpython/_compiler/program.py-427- if isinstance(node, A.BinOp): +asmpython/_compiler/program.py-428- _free_names(node.left, out) +asmpython/_compiler/program.py-429- _free_names(node.right, out) +asmpython/_compiler/program.py-430- return +-- +asmpython/_compiler/program.py-1031- elif isinstance(s, A.YieldStmt): +asmpython/_compiler/program.py-1032- _rename_call_targets_expr(s.value, renames) +asmpython/_compiler/program.py-1033- elif isinstance(s, A.Match): +asmpython/_compiler/program.py-1034- _rename_call_targets_expr(s.subject, renames) +asmpython/_compiler/program.py-1035- for _pattern, guard, body in s.cases: +asmpython/_compiler/program.py-1036- if guard is not None: +asmpython/_compiler/program.py-1037- _rename_call_targets_expr(guard, renames) +asmpython/_compiler/program.py-1038- _rename_call_targets(body, renames) +asmpython/_compiler/program.py-1039- elif isinstance(s, A.ClosureBind) and s.func_name in renames: +asmpython/_compiler/program.py-1040- s.func_name = renames[s.func_name] +asmpython/_compiler/program.py-1041- +asmpython/_compiler/program.py-1042- +asmpython/_compiler/program.py-1043-def _rename_call_targets_expr(e, renames: dict[str, str]) -> None: +asmpython/_compiler/program.py-1044- if isinstance(e, A.Call): +asmpython/_compiler/program.py-1045- if e.func in renames: +asmpython/_compiler/program.py-1046- e.func = renames[e.func] +asmpython/_compiler/program.py-1047- for a in e.args: +asmpython/_compiler/program.py-1048- _rename_call_targets_expr(a, renames) +asmpython/_compiler/program.py-1049- for _kn, kv in e.kwargs: +asmpython/_compiler/program.py-1050- _rename_call_targets_expr(kv, renames) +asmpython/_compiler/program.py:1051: elif isinstance(e, A.MethodCall): +asmpython/_compiler/program.py-1052- _rename_call_targets_expr(e.obj, renames) +asmpython/_compiler/program.py-1053- for a in e.args: +asmpython/_compiler/program.py-1054- _rename_call_targets_expr(a, renames) +asmpython/_compiler/program.py-1055- for _kn, kv in e.kwargs: +asmpython/_compiler/program.py-1056- _rename_call_targets_expr(kv, renames) +asmpython/_compiler/program.py-1057- elif isinstance(e, A.BinOp): +asmpython/_compiler/program.py-1058- _rename_call_targets_expr(e.left, renames) +asmpython/_compiler/program.py-1059- _rename_call_targets_expr(e.right, renames) +asmpython/_compiler/program.py-1060- elif isinstance(e, A.UnaryOp): +asmpython/_compiler/program.py-1061- _rename_call_targets_expr(e.operand, renames) +asmpython/_compiler/program.py-1062- elif isinstance(e, A.Compare): +asmpython/_compiler/program.py-1063- for o in e.operands: +asmpython/_compiler/program.py-1064- _rename_call_targets_expr(o, renames) +asmpython/_compiler/program.py-1065- elif isinstance(e, A.BoolOp): +asmpython/_compiler/program.py-1066- _rename_call_targets_expr(e.left, renames) +asmpython/_compiler/program.py-1067- _rename_call_targets_expr(e.right, renames) +asmpython/_compiler/program.py-1068- elif isinstance(e, A.IfExp): +asmpython/_compiler/program.py-1069- _rename_call_targets_expr(e.test, renames) +asmpython/_compiler/program.py-1070- _rename_call_targets_expr(e.body, renames) +asmpython/_compiler/program.py-1071- _rename_call_targets_expr(e.orelse, renames) +asmpython/_compiler/program.py-1072- elif isinstance(e, A.NamedExpr): +asmpython/_compiler/program.py-1073- _rename_call_targets_expr(e.value, renames) +asmpython/_compiler/program.py-1074- elif isinstance(e, A.ListLit): +asmpython/_compiler/program.py-1075- for el in e.elems: +asmpython/_compiler/program.py-1076- _rename_call_targets_expr(el, renames) +asmpython/_compiler/program.py-1077- elif isinstance(e, A.Subscript): +asmpython/_compiler/program.py-1078- _rename_call_targets_expr(e.obj, renames) +asmpython/_compiler/program.py-1079- if isinstance(e.index, A.Slice): +asmpython/_compiler/program.py-1080- if e.index.start is not None: +asmpython/_compiler/program.py-1081- _rename_call_targets_expr(e.index.start, renames) +asmpython/_compiler/program.py-1082- if e.index.stop is not None: +asmpython/_compiler/program.py-1083- _rename_call_targets_expr(e.index.stop, renames) +asmpython/_compiler/program.py-1084- if e.index.step is not None: +asmpython/_compiler/program.py-1085- _rename_call_targets_expr(e.index.step, renames) +asmpython/_compiler/program.py-1086- else: +asmpython/_compiler/program.py-1087- _rename_call_targets_expr(e.index, renames) +asmpython/_compiler/program.py-1088- elif isinstance(e, A.Attr): +asmpython/_compiler/program.py-1089- _rename_call_targets_expr(e.obj, renames) +asmpython/_compiler/program.py-1090- elif isinstance(e, A.FString): +asmpython/_compiler/program.py-1091- for seg in e.segments: +asmpython/_compiler/program.py-1092- _rename_call_targets_expr(seg, renames) +asmpython/_compiler/program.py-1093- elif isinstance(e, A.DictLit): +asmpython/_compiler/program.py-1094- for k in e.keys: +asmpython/_compiler/program.py-1095- if k is not None: +asmpython/_compiler/program.py-1096- _rename_call_targets_expr(k, renames) +asmpython/_compiler/program.py-1097- for v in e.values: +asmpython/_compiler/program.py-1098- _rename_call_targets_expr(v, renames) +asmpython/_compiler/program.py-1099- elif isinstance(e, A.TupleLit): +asmpython/_compiler/program.py-1100- for el in e.elems: +asmpython/_compiler/program.py-1101- _rename_call_targets_expr(el, renames) +asmpython/_compiler/program.py-1102- elif isinstance(e, A.SetLit): +asmpython/_compiler/program.py-1103- for el in e.elems: +asmpython/_compiler/program.py-1104- _rename_call_targets_expr(el, renames) +asmpython/_compiler/program.py-1105- elif isinstance(e, A.Starred): +asmpython/_compiler/program.py-1106- _rename_call_targets_expr(e.value, renames) +asmpython/_compiler/program.py-1107- elif isinstance(e, A.Comprehension): +asmpython/_compiler/program.py-1108- _rename_call_targets_expr(e.elt, renames) +asmpython/_compiler/program.py-1109- _rename_call_targets_expr(e.iter, renames) +asmpython/_compiler/program.py-1110- if e.cond is not None: +asmpython/_compiler/program.py-1111- _rename_call_targets_expr(e.cond, renames) +-- +asmpython/_compiler/parser.py-199- # clause(s) — Python scopes these to the comprehension itself, not +asmpython/_compiler/parser.py-200- # the surrounding function, so a `Name` read while this is non-empty +asmpython/_compiler/parser.py-201- # must not be recorded as a free-var reference. A list-as-stack +asmpython/_compiler/parser.py-202- # (rather than reassigning a set) so nested comprehensions compose +asmpython/_compiler/parser.py-203- # without needing `nonlocal`. +asmpython/_compiler/parser.py-204- comp_suppressed: list = [] +asmpython/_compiler/parser.py-205- def _collect_refs_expr(node) -> None: +asmpython/_compiler/parser.py-206- if isinstance(node, A.Name): +asmpython/_compiler/parser.py-207- if node.name not in comp_suppressed: +asmpython/_compiler/parser.py-208- referenced.add(node.name) +asmpython/_compiler/parser.py-209- elif isinstance(node, A.BinOp): +asmpython/_compiler/parser.py-210- _collect_refs_expr(node.left) +asmpython/_compiler/parser.py-211- _collect_refs_expr(node.right) +asmpython/_compiler/parser.py-212- elif isinstance(node, A.UnaryOp): +asmpython/_compiler/parser.py-213- _collect_refs_expr(node.operand) +asmpython/_compiler/parser.py-214- elif isinstance(node, A.Call): +asmpython/_compiler/parser.py-215- for a in node.args: +asmpython/_compiler/parser.py-216- _collect_refs_expr(a) +asmpython/_compiler/parser.py-217- for _kw_name, kw_val in (node.kwargs or []): +asmpython/_compiler/parser.py-218- _collect_refs_expr(kw_val) +asmpython/_compiler/parser.py:219: elif isinstance(node, A.MethodCall): +asmpython/_compiler/parser.py-220- _collect_refs_expr(node.obj) +asmpython/_compiler/parser.py-221- for a in node.args: +asmpython/_compiler/parser.py-222- _collect_refs_expr(a) +asmpython/_compiler/parser.py-223- for _kw_name, kw_val in (node.kwargs or []): +asmpython/_compiler/parser.py-224- _collect_refs_expr(kw_val) +asmpython/_compiler/parser.py-225- elif isinstance(node, A.Attr): +asmpython/_compiler/parser.py-226- _collect_refs_expr(node.obj) +asmpython/_compiler/parser.py-227- elif isinstance(node, A.Subscript): +asmpython/_compiler/parser.py-228- _collect_refs_expr(node.obj) +asmpython/_compiler/parser.py-229- _collect_refs_expr(node.index) +asmpython/_compiler/parser.py-230- elif isinstance(node, A.Slice): +asmpython/_compiler/parser.py-231- if node.start is not None: +asmpython/_compiler/parser.py-232- _collect_refs_expr(node.start) +asmpython/_compiler/parser.py-233- if node.stop is not None: +asmpython/_compiler/parser.py-234- _collect_refs_expr(node.stop) +asmpython/_compiler/parser.py-235- if node.step is not None: +asmpython/_compiler/parser.py-236- _collect_refs_expr(node.step) +asmpython/_compiler/parser.py-237- elif isinstance(node, A.IfExp): +asmpython/_compiler/parser.py-238- _collect_refs_expr(node.test) +asmpython/_compiler/parser.py-239- _collect_refs_expr(node.body) +asmpython/_compiler/parser.py-240- _collect_refs_expr(node.orelse) +asmpython/_compiler/parser.py-241- elif isinstance(node, A.NamedExpr): +asmpython/_compiler/parser.py-242- _collect_refs_expr(node.value) +asmpython/_compiler/parser.py-243- elif isinstance(node, A.BoolOp): +asmpython/_compiler/parser.py-244- _collect_refs_expr(node.left) +asmpython/_compiler/parser.py-245- _collect_refs_expr(node.right) +asmpython/_compiler/parser.py-246- elif isinstance(node, A.Compare): +asmpython/_compiler/parser.py-247- for op in node.operands: +asmpython/_compiler/parser.py-248- _collect_refs_expr(op) +asmpython/_compiler/parser.py-249- elif isinstance(node, A.ListLit): +asmpython/_compiler/parser.py-250- for e in node.elems: +asmpython/_compiler/parser.py-251- _collect_refs_expr(e) +asmpython/_compiler/parser.py-252- elif isinstance(node, A.TupleLit): +asmpython/_compiler/parser.py-253- for e in node.elems: +asmpython/_compiler/parser.py-254- _collect_refs_expr(e) +asmpython/_compiler/parser.py-255- elif isinstance(node, A.SetLit): +asmpython/_compiler/parser.py-256- for e in node.elems: +asmpython/_compiler/parser.py-257- _collect_refs_expr(e) +asmpython/_compiler/parser.py-258- elif isinstance(node, A.DictLit): +asmpython/_compiler/parser.py-259- for k in node.keys: +asmpython/_compiler/parser.py-260- if k is not None: +asmpython/_compiler/parser.py-261- _collect_refs_expr(k) +asmpython/_compiler/parser.py-262- for v in node.values: +asmpython/_compiler/parser.py-263- _collect_refs_expr(v) +asmpython/_compiler/parser.py-264- elif isinstance(node, A.FString): +asmpython/_compiler/parser.py-265- for seg in node.segments: +asmpython/_compiler/parser.py-266- _collect_refs_expr(seg) +asmpython/_compiler/parser.py-267- elif isinstance(node, A.Starred): +asmpython/_compiler/parser.py-268- _collect_refs_expr(node.value) +asmpython/_compiler/parser.py-269- elif isinstance(node, A.Lambda): +asmpython/_compiler/parser.py-270- if node.body is not None: +asmpython/_compiler/parser.py-271- _collect_refs_expr(node.body) +asmpython/_compiler/parser.py-272- elif isinstance(node, A.Comprehension): +asmpython/_compiler/parser.py-273- # `iter` (the outermost `for x in `) runs in the +asmpython/_compiler/parser.py-274- # *enclosing* scope in real Python, so walk it before any +asmpython/_compiler/parser.py-275- # suppression is pushed. +asmpython/_compiler/parser.py-276- _collect_refs_expr(node.iter) +asmpython/_compiler/parser.py-277- _comp_vars: list = [] +asmpython/_compiler/parser.py-278- if node.var: +asmpython/_compiler/parser.py-279- _comp_vars.append(node.var) +-- +asmpython/_compiler/parser.py-512- Parser._collect_called_names_expr(s.value, out) +asmpython/_compiler/parser.py-513- elif isinstance(s, A.Del): +asmpython/_compiler/parser.py-514- Parser._collect_called_names_expr(s.target, out) +asmpython/_compiler/parser.py-515- elif isinstance(s, A.YieldStmt): +asmpython/_compiler/parser.py-516- Parser._collect_called_names_expr(s.value, out) +asmpython/_compiler/parser.py-517- elif isinstance(s, A.Match): +asmpython/_compiler/parser.py-518- Parser._collect_called_names_expr(s.subject, out) +asmpython/_compiler/parser.py-519- for _pattern, guard, body in s.cases: +asmpython/_compiler/parser.py-520- if guard is not None: +asmpython/_compiler/parser.py-521- Parser._collect_called_names_expr(guard, out) +asmpython/_compiler/parser.py-522- Parser._collect_called_names(body, out) +asmpython/_compiler/parser.py-523- +asmpython/_compiler/parser.py-524- @staticmethod +asmpython/_compiler/parser.py-525- def _collect_called_names_expr(e, out: set) -> None: +asmpython/_compiler/parser.py-526- if isinstance(e, A.Call): +asmpython/_compiler/parser.py-527- out.add(e.func) +asmpython/_compiler/parser.py-528- for a in e.args: +asmpython/_compiler/parser.py-529- Parser._collect_called_names_expr(a, out) +asmpython/_compiler/parser.py-530- for _kn, kv in e.kwargs: +asmpython/_compiler/parser.py-531- Parser._collect_called_names_expr(kv, out) +asmpython/_compiler/parser.py:532: elif isinstance(e, A.MethodCall): +asmpython/_compiler/parser.py-533- Parser._collect_called_names_expr(e.obj, out) +asmpython/_compiler/parser.py-534- for a in e.args: +asmpython/_compiler/parser.py-535- Parser._collect_called_names_expr(a, out) +asmpython/_compiler/parser.py-536- for _kn, kv in e.kwargs: +asmpython/_compiler/parser.py-537- Parser._collect_called_names_expr(kv, out) +asmpython/_compiler/parser.py-538- elif isinstance(e, A.BinOp): +asmpython/_compiler/parser.py-539- Parser._collect_called_names_expr(e.left, out) +asmpython/_compiler/parser.py-540- Parser._collect_called_names_expr(e.right, out) +asmpython/_compiler/parser.py-541- elif isinstance(e, A.UnaryOp): +asmpython/_compiler/parser.py-542- Parser._collect_called_names_expr(e.operand, out) +asmpython/_compiler/parser.py-543- elif isinstance(e, A.Compare): +asmpython/_compiler/parser.py-544- for o in e.operands: +asmpython/_compiler/parser.py-545- Parser._collect_called_names_expr(o, out) +asmpython/_compiler/parser.py-546- elif isinstance(e, A.BoolOp): +asmpython/_compiler/parser.py-547- Parser._collect_called_names_expr(e.left, out) +asmpython/_compiler/parser.py-548- Parser._collect_called_names_expr(e.right, out) +asmpython/_compiler/parser.py-549- elif isinstance(e, A.IfExp): +asmpython/_compiler/parser.py-550- Parser._collect_called_names_expr(e.test, out) +asmpython/_compiler/parser.py-551- Parser._collect_called_names_expr(e.body, out) +asmpython/_compiler/parser.py-552- Parser._collect_called_names_expr(e.orelse, out) +asmpython/_compiler/parser.py-553- elif isinstance(e, A.NamedExpr): +asmpython/_compiler/parser.py-554- Parser._collect_called_names_expr(e.value, out) +asmpython/_compiler/parser.py-555- elif isinstance(e, A.ListLit): +asmpython/_compiler/parser.py-556- for el in e.elems: +asmpython/_compiler/parser.py-557- Parser._collect_called_names_expr(el, out) +asmpython/_compiler/parser.py-558- elif isinstance(e, A.Subscript): +asmpython/_compiler/parser.py-559- Parser._collect_called_names_expr(e.obj, out) +asmpython/_compiler/parser.py-560- if isinstance(e.index, A.Slice): +asmpython/_compiler/parser.py-561- if e.index.start is not None: +asmpython/_compiler/parser.py-562- Parser._collect_called_names_expr(e.index.start, out) +asmpython/_compiler/parser.py-563- if e.index.stop is not None: +asmpython/_compiler/parser.py-564- Parser._collect_called_names_expr(e.index.stop, out) +asmpython/_compiler/parser.py-565- if e.index.step is not None: +asmpython/_compiler/parser.py-566- Parser._collect_called_names_expr(e.index.step, out) +asmpython/_compiler/parser.py-567- else: +asmpython/_compiler/parser.py-568- Parser._collect_called_names_expr(e.index, out) +asmpython/_compiler/parser.py-569- elif isinstance(e, A.Attr): +asmpython/_compiler/parser.py-570- Parser._collect_called_names_expr(e.obj, out) +asmpython/_compiler/parser.py-571- elif isinstance(e, A.FString): +asmpython/_compiler/parser.py-572- for seg in e.segments: +asmpython/_compiler/parser.py-573- Parser._collect_called_names_expr(seg, out) +asmpython/_compiler/parser.py-574- elif isinstance(e, A.DictLit): +asmpython/_compiler/parser.py-575- for k in e.keys: +asmpython/_compiler/parser.py-576- if k is not None: +asmpython/_compiler/parser.py-577- Parser._collect_called_names_expr(k, out) +asmpython/_compiler/parser.py-578- for v in e.values: +asmpython/_compiler/parser.py-579- Parser._collect_called_names_expr(v, out) +asmpython/_compiler/parser.py-580- elif isinstance(e, A.TupleLit): +asmpython/_compiler/parser.py-581- for el in e.elems: +asmpython/_compiler/parser.py-582- Parser._collect_called_names_expr(el, out) +asmpython/_compiler/parser.py-583- elif isinstance(e, A.SetLit): +asmpython/_compiler/parser.py-584- for el in e.elems: +asmpython/_compiler/parser.py-585- Parser._collect_called_names_expr(el, out) +asmpython/_compiler/parser.py-586- elif isinstance(e, A.Starred): +asmpython/_compiler/parser.py-587- Parser._collect_called_names_expr(e.value, out) +asmpython/_compiler/parser.py-588- elif isinstance(e, A.Comprehension): +asmpython/_compiler/parser.py-589- Parser._collect_called_names_expr(e.elt, out) +asmpython/_compiler/parser.py-590- Parser._collect_called_names_expr(e.iter, out) +asmpython/_compiler/parser.py-591- if e.cond is not None: +asmpython/_compiler/parser.py-592- Parser._collect_called_names_expr(e.cond, out) +-- +asmpython/_compiler/parser.py-3090- if not self._check("OP", ":") and not self._check("OP", "]"): +asmpython/_compiler/parser.py-3091- stop = self._parse_expr() +asmpython/_compiler/parser.py-3092- if self._check("OP", ":"): +asmpython/_compiler/parser.py-3093- self._eat() +asmpython/_compiler/parser.py-3094- if not self._check("OP", "]"): +asmpython/_compiler/parser.py-3095- step = self._parse_expr() +asmpython/_compiler/parser.py-3096- self._expect("OP", "]") +asmpython/_compiler/parser.py-3097- idx = A.Slice(start=start, stop=stop, step=step, pos=lbr.pos) +asmpython/_compiler/parser.py-3098- else: +asmpython/_compiler/parser.py-3099- self._expect("OP", "]") +asmpython/_compiler/parser.py-3100- idx = start +asmpython/_compiler/parser.py-3101- atom = A.Subscript(obj=atom, index=idx, pos=lbr.pos) # type: ignore +asmpython/_compiler/parser.py-3102- elif self._check("OP", "."): +asmpython/_compiler/parser.py-3103- dot = self._eat() +asmpython/_compiler/parser.py-3104- name = self._expect("NAME").value +asmpython/_compiler/parser.py-3105- if self._check("OP", "("): +asmpython/_compiler/parser.py-3106- # obj.name(...) — method call +asmpython/_compiler/parser.py-3107- self._eat() +asmpython/_compiler/parser.py-3108- args, kwargs = self._parse_call_args() # type: ignore +asmpython/_compiler/parser.py-3109- self._expect("OP", ")") +asmpython/_compiler/parser.py:3110: atom = A.MethodCall( +asmpython/_compiler/parser.py-3111- obj=atom, +asmpython/_compiler/parser.py-3112- method=name, # type: ignore +asmpython/_compiler/parser.py-3113- args=args, +asmpython/_compiler/parser.py-3114- kwargs=kwargs, +asmpython/_compiler/parser.py-3115- pos=dot.pos, # type: ignore +asmpython/_compiler/parser.py-3116- ) +asmpython/_compiler/parser.py-3117- else: +asmpython/_compiler/parser.py-3118- # obj.name — attribute access (e.g. math.pi) +asmpython/_compiler/parser.py-3119- atom = A.Attr(obj=atom, name=name, pos=dot.pos) # type: ignore +asmpython/_compiler/parser.py-3120- else: +asmpython/_compiler/parser.py-3121- return atom +asmpython/_compiler/parser.py-3122- +asmpython/_compiler/parser.py-3123- def _parse_paren_or_tuple(self): +asmpython/_compiler/parser.py-3124- """After a '(': parse either a parenthesised expression or a tuple. +asmpython/_compiler/parser.py-3125- +asmpython/_compiler/parser.py-3126- A comma is what makes it a tuple: +asmpython/_compiler/parser.py-3127- () -> empty tuple +asmpython/_compiler/parser.py-3128- (a) -> just `a` (grouping, not a tuple) +asmpython/_compiler/parser.py-3129- (a,) -> 1-tuple +asmpython/_compiler/parser.py-3130- (a, b, c) -> 3-tuple (trailing comma allowed) +asmpython/_compiler/parser.py-3131- """ +asmpython/_compiler/parser.py-3132- lpar = self._expect("OP", "(") +asmpython/_compiler/parser.py-3133- if self._check("OP", ")"): +asmpython/_compiler/parser.py-3134- self._eat() +asmpython/_compiler/parser.py-3135- return A.TupleLit(elems=[], pos=lpar.pos) +asmpython/_compiler/parser.py-3136- tuple_has_star = False +asmpython/_compiler/parser.py-3137- if self._check("OP", "*"): +asmpython/_compiler/parser.py-3138- star_pos = self._eat().pos +asmpython/_compiler/parser.py-3139- first = A.Starred(value=self._parse_expr(), pos=star_pos) +asmpython/_compiler/parser.py-3140- tuple_has_star = True +asmpython/_compiler/parser.py-3141- else: +asmpython/_compiler/parser.py-3142- first = self._parse_expr() +asmpython/_compiler/parser.py-3143- if self._check("KEYWORD", "for"): +asmpython/_compiler/parser.py-3144- if isinstance(first, A.Starred): +asmpython/_compiler/parser.py-3145- raise ParseError( +asmpython/_compiler/parser.py-3146- "generator expression element cannot be starred", +asmpython/_compiler/parser.py-3147- first.pos, +asmpython/_compiler/parser.py-3148- ) +asmpython/_compiler/parser.py-3149- comp = self._parse_comprehension_tail(first, lpar.pos) +asmpython/_compiler/parser.py-3150- self._expect("OP", ")") +asmpython/_compiler/parser.py-3151- return comp +asmpython/_compiler/parser.py-3152- if not self._check("OP", ","): +asmpython/_compiler/parser.py-3153- self._expect("OP", ")") +asmpython/_compiler/parser.py-3154- return first +asmpython/_compiler/parser.py-3155- elems = [first] +asmpython/_compiler/parser.py-3156- while self._check("OP", ","): +asmpython/_compiler/parser.py-3157- self._eat() +asmpython/_compiler/parser.py-3158- if self._check("OP", ")"): +asmpython/_compiler/parser.py-3159- break # trailing comma +asmpython/_compiler/parser.py-3160- if self._check("OP", "*"): +asmpython/_compiler/parser.py-3161- star_pos2 = self._eat().pos +asmpython/_compiler/parser.py-3162- elems.append(A.Starred(value=self._parse_expr(), pos=star_pos2)) +asmpython/_compiler/parser.py-3163- tuple_has_star = True +asmpython/_compiler/parser.py-3164- else: +asmpython/_compiler/parser.py-3165- elems.append(self._parse_expr()) +asmpython/_compiler/parser.py-3166- self._expect("OP", ")") +asmpython/_compiler/parser.py-3167- if tuple_has_star: +asmpython/_compiler/parser.py-3168- return A.ListLit(elems=elems, pos=lpar.pos) +asmpython/_compiler/parser.py-3169- return A.TupleLit(elems=elems, pos=lpar.pos) +asmpython/_compiler/parser.py-3170- +-- +-- +-- +-- +asmpython/_compiler/type_parameter_compat_fixes.py-120- return result +asmpython/_compiler/type_parameter_compat_fixes.py-121- +asmpython/_compiler/type_parameter_compat_fixes.py-122- +asmpython/_compiler/type_parameter_compat_fixes.py-123-def _assignment_related_variables(func, parameter: str) -> set: +asmpython/_compiler/type_parameter_compat_fixes.py-124- related = set() +asmpython/_compiler/type_parameter_compat_fixes.py-125- changed = True +asmpython/_compiler/type_parameter_compat_fixes.py-126- while changed: +asmpython/_compiler/type_parameter_compat_fixes.py-127- changed = False +asmpython/_compiler/type_parameter_compat_fixes.py-128- for node in _walk_stmts(func.body): +asmpython/_compiler/type_parameter_compat_fixes.py-129- if not isinstance(node, A.Assign): +asmpython/_compiler/type_parameter_compat_fixes.py-130- continue +asmpython/_compiler/type_parameter_compat_fixes.py-131- value = node.value +asmpython/_compiler/type_parameter_compat_fixes.py-132- uses_parameter = _expression_uses_type_parameter(value, parameter) +asmpython/_compiler/type_parameter_compat_fixes.py-133- uses_related = any( +asmpython/_compiler/type_parameter_compat_fixes.py-134- isinstance(expr, A.Name) and expr.name in related +asmpython/_compiler/type_parameter_compat_fixes.py-135- for expr in _walk_expr(value) +asmpython/_compiler/type_parameter_compat_fixes.py-136- ) +asmpython/_compiler/type_parameter_compat_fixes.py-137- passes_parameter = any( +asmpython/_compiler/type_parameter_compat_fixes.py-138- isinstance(expr, A.Name) and expr.name == parameter +asmpython/_compiler/type_parameter_compat_fixes.py-139- for expr in _walk_expr(value) +asmpython/_compiler/type_parameter_compat_fixes.py:140: ) and isinstance(value, (A.Call, A.MethodCall)) +asmpython/_compiler/type_parameter_compat_fixes.py-141- if ( +asmpython/_compiler/type_parameter_compat_fixes.py-142- uses_parameter or uses_related or passes_parameter +asmpython/_compiler/type_parameter_compat_fixes.py-143- ) and node.target not in related: +asmpython/_compiler/type_parameter_compat_fixes.py-144- related.add(node.target) +asmpython/_compiler/type_parameter_compat_fixes.py-145- changed = True +asmpython/_compiler/type_parameter_compat_fixes.py-146- return related +asmpython/_compiler/type_parameter_compat_fixes.py-147- +asmpython/_compiler/type_parameter_compat_fixes.py-148- +asmpython/_compiler/type_parameter_compat_fixes.py-149-def _guarded_type_variable(test, parameter: str) -> "str | None": +asmpython/_compiler/type_parameter_compat_fixes.py-150- for expr in _walk_expr(test): +asmpython/_compiler/type_parameter_compat_fixes.py-151- if ( +asmpython/_compiler/type_parameter_compat_fixes.py-152- isinstance(expr, A.Call) +asmpython/_compiler/type_parameter_compat_fixes.py-153- and expr.func == "isinstance" +asmpython/_compiler/type_parameter_compat_fixes.py-154- and len(expr.args) == 2 +asmpython/_compiler/type_parameter_compat_fixes.py-155- and isinstance(expr.args[0], A.Name) +asmpython/_compiler/type_parameter_compat_fixes.py-156- and isinstance(expr.args[1], A.Name) +asmpython/_compiler/type_parameter_compat_fixes.py-157- and expr.args[1].name == parameter +asmpython/_compiler/type_parameter_compat_fixes.py-158- ): +asmpython/_compiler/type_parameter_compat_fixes.py-159- return expr.args[0].name +asmpython/_compiler/type_parameter_compat_fixes.py-160- return None +asmpython/_compiler/type_parameter_compat_fixes.py-161- +asmpython/_compiler/type_parameter_compat_fixes.py-162- +asmpython/_compiler/type_parameter_compat_fixes.py-163-def _returns_specialized_type(func, parameter: str) -> bool: +asmpython/_compiler/type_parameter_compat_fixes.py-164- related = _assignment_related_variables(func, parameter) +asmpython/_compiler/type_parameter_compat_fixes.py-165- saw_value = False +asmpython/_compiler/type_parameter_compat_fixes.py-166- valid = True +asmpython/_compiler/type_parameter_compat_fixes.py-167- +asmpython/_compiler/type_parameter_compat_fixes.py-168- def visit(stmts: list, guarded: set) -> None: +asmpython/_compiler/type_parameter_compat_fixes.py-169- nonlocal saw_value, valid +asmpython/_compiler/type_parameter_compat_fixes.py-170- for stmt in stmts: +asmpython/_compiler/type_parameter_compat_fixes.py-171- if isinstance(stmt, A.Return): +asmpython/_compiler/type_parameter_compat_fixes.py-172- value = stmt.value +asmpython/_compiler/type_parameter_compat_fixes.py-173- if value is None or ( +asmpython/_compiler/type_parameter_compat_fixes.py-174- isinstance(value, A.IntLit) and getattr(value, "is_none", False) +asmpython/_compiler/type_parameter_compat_fixes.py-175- ): +asmpython/_compiler/type_parameter_compat_fixes.py-176- continue +asmpython/_compiler/type_parameter_compat_fixes.py-177- saw_value = True +asmpython/_compiler/type_parameter_compat_fixes.py-178- if isinstance(value, A.Call) and value.func == parameter: +asmpython/_compiler/type_parameter_compat_fixes.py-179- continue +asmpython/_compiler/type_parameter_compat_fixes.py-180- if isinstance(value, A.Name) and ( +asmpython/_compiler/type_parameter_compat_fixes.py-181- value.name in related or value.name in guarded +asmpython/_compiler/type_parameter_compat_fixes.py-182- ): +asmpython/_compiler/type_parameter_compat_fixes.py-183- continue +asmpython/_compiler/type_parameter_compat_fixes.py-184- valid = False +asmpython/_compiler/type_parameter_compat_fixes.py-185- continue +asmpython/_compiler/type_parameter_compat_fixes.py-186- if isinstance(stmt, A.If): +asmpython/_compiler/type_parameter_compat_fixes.py-187- narrowed = _guarded_type_variable(stmt.test, parameter) +asmpython/_compiler/type_parameter_compat_fixes.py-188- then_guarded = set(guarded) +asmpython/_compiler/type_parameter_compat_fixes.py-189- if narrowed is not None: +asmpython/_compiler/type_parameter_compat_fixes.py-190- then_guarded.add(narrowed) +asmpython/_compiler/type_parameter_compat_fixes.py-191- visit(stmt.then, then_guarded) +asmpython/_compiler/type_parameter_compat_fixes.py-192- visit(stmt.orelse, guarded) +asmpython/_compiler/type_parameter_compat_fixes.py-193- continue +asmpython/_compiler/type_parameter_compat_fixes.py-194- for attr in ("body", "handler", "else_body", "finally_body"): +asmpython/_compiler/type_parameter_compat_fixes.py-195- nested = getattr(stmt, attr, None) +asmpython/_compiler/type_parameter_compat_fixes.py-196- if isinstance(nested, list): +asmpython/_compiler/type_parameter_compat_fixes.py-197- visit(nested, guarded) +asmpython/_compiler/type_parameter_compat_fixes.py-198- +asmpython/_compiler/type_parameter_compat_fixes.py-199- visit(func.body, set()) +asmpython/_compiler/type_parameter_compat_fixes.py-200- return saw_value and valid +asmpython/_compiler/type_parameter_compat_fixes.py-201- +asmpython/_compiler/type_parameter_compat_fixes.py-202- +asmpython/_compiler/type_parameter_compat_fixes.py-203-def _all_statement_lists(mod: A.Module) -> list: +asmpython/_compiler/type_parameter_compat_fixes.py-204- result = [mod.body] +asmpython/_compiler/type_parameter_compat_fixes.py-205- for func in mod.funcs: +asmpython/_compiler/type_parameter_compat_fixes.py-206- result.append(func.body) +asmpython/_compiler/type_parameter_compat_fixes.py-207- for cls in mod.classes: +asmpython/_compiler/type_parameter_compat_fixes.py-208- for method in cls.methods: +asmpython/_compiler/type_parameter_compat_fixes.py-209- result.append(method.body) +asmpython/_compiler/type_parameter_compat_fixes.py-210- return result +asmpython/_compiler/type_parameter_compat_fixes.py-211- +asmpython/_compiler/type_parameter_compat_fixes.py-212- +asmpython/_compiler/type_parameter_compat_fixes.py-213-def _all_call_nodes(mod: A.Module) -> list: +asmpython/_compiler/type_parameter_compat_fixes.py-214- calls: list = [] +asmpython/_compiler/type_parameter_compat_fixes.py-215- for stmts in _all_statement_lists(mod): +asmpython/_compiler/type_parameter_compat_fixes.py-216- for node in _walk_stmts(stmts): +asmpython/_compiler/type_parameter_compat_fixes.py:217: if isinstance(node, (A.Call, A.MethodCall)): +asmpython/_compiler/type_parameter_compat_fixes.py-218- calls.append(node) +asmpython/_compiler/type_parameter_compat_fixes.py-219- return calls +asmpython/_compiler/type_parameter_compat_fixes.py-220- +asmpython/_compiler/type_parameter_compat_fixes.py-221- +asmpython/_compiler/type_parameter_compat_fixes.py-222-def _call_sites_with_owners(mod: A.Module): +asmpython/_compiler/type_parameter_compat_fixes.py-223- """Yield ``(owning FuncDef id or None, call)`` for the complete module.""" +asmpython/_compiler/type_parameter_compat_fixes.py-224- for node in _walk_stmts(mod.body): +asmpython/_compiler/type_parameter_compat_fixes.py:225: if isinstance(node, (A.Call, A.MethodCall)): +asmpython/_compiler/type_parameter_compat_fixes.py-226- yield (None, node) +asmpython/_compiler/type_parameter_compat_fixes.py-227- for func in mod.funcs: +asmpython/_compiler/type_parameter_compat_fixes.py-228- owner_id = id(func) +asmpython/_compiler/type_parameter_compat_fixes.py-229- for node in _walk_stmts(func.body): +asmpython/_compiler/type_parameter_compat_fixes.py:230: if isinstance(node, (A.Call, A.MethodCall)): +asmpython/_compiler/type_parameter_compat_fixes.py-231- yield (owner_id, node) +asmpython/_compiler/type_parameter_compat_fixes.py-232- for cls in mod.classes: +asmpython/_compiler/type_parameter_compat_fixes.py-233- for method in cls.methods: +asmpython/_compiler/type_parameter_compat_fixes.py-234- owner_id = id(method) +asmpython/_compiler/type_parameter_compat_fixes.py-235- for node in _walk_stmts(method.body): +asmpython/_compiler/type_parameter_compat_fixes.py:236: if isinstance(node, (A.Call, A.MethodCall)): +asmpython/_compiler/type_parameter_compat_fixes.py-237- yield (owner_id, node) +asmpython/_compiler/type_parameter_compat_fixes.py-238- +asmpython/_compiler/type_parameter_compat_fixes.py-239- +asmpython/_compiler/type_parameter_compat_fixes.py-240-def _argument_binding(call, func, parameter_index: int, is_method: bool): +asmpython/_compiler/type_parameter_compat_fixes.py-241- parameter = func.params[parameter_index] +asmpython/_compiler/type_parameter_compat_fixes.py-242- offset = 0 +asmpython/_compiler/type_parameter_compat_fixes.py-243- if is_method and "staticmethod" not in getattr(func, "decorators", []): +asmpython/_compiler/type_parameter_compat_fixes.py-244- offset = 1 +asmpython/_compiler/type_parameter_compat_fixes.py-245- positional_index = parameter_index - offset +asmpython/_compiler/type_parameter_compat_fixes.py-246- if 0 <= positional_index < len(call.args): +asmpython/_compiler/type_parameter_compat_fixes.py-247- return call.args[positional_index], ("positional", positional_index) +asmpython/_compiler/type_parameter_compat_fixes.py-248- for index, (name, value) in enumerate(call.kwargs): +asmpython/_compiler/type_parameter_compat_fixes.py-249- if name == parameter: +asmpython/_compiler/type_parameter_compat_fixes.py-250- return value, ("keyword", index) +asmpython/_compiler/type_parameter_compat_fixes.py-251- return None, None +asmpython/_compiler/type_parameter_compat_fixes.py-252- +asmpython/_compiler/type_parameter_compat_fixes.py-253- +asmpython/_compiler/type_parameter_compat_fixes.py-254-def _remove_bound_argument(call, binding) -> None: +asmpython/_compiler/type_parameter_compat_fixes.py-255- kind, index = binding +asmpython/_compiler/type_parameter_compat_fixes.py-256- if kind == "positional": +asmpython/_compiler/type_parameter_compat_fixes.py-257- del call.args[index] +asmpython/_compiler/type_parameter_compat_fixes.py-258- else: +asmpython/_compiler/type_parameter_compat_fixes.py-259- del call.kwargs[index] +asmpython/_compiler/type_parameter_compat_fixes.py-260- +asmpython/_compiler/type_parameter_compat_fixes.py-261- +asmpython/_compiler/type_parameter_compat_fixes.py-262-def _sanitize(name: str) -> str: +asmpython/_compiler/type_parameter_compat_fixes.py-263- out = [] +asmpython/_compiler/type_parameter_compat_fixes.py-264- for char in name: +asmpython/_compiler/type_parameter_compat_fixes.py-265- out.append(char if char.isalnum() or char == "_" else "_") +asmpython/_compiler/type_parameter_compat_fixes.py-266- return "".join(out) +asmpython/_compiler/type_parameter_compat_fixes.py-267- +asmpython/_compiler/type_parameter_compat_fixes.py-268- +asmpython/_compiler/type_parameter_compat_fixes.py-269-def _specialized_clone(func, parameter_index: int, class_name: str, clone_name: str): +asmpython/_compiler/type_parameter_compat_fixes.py-270- cloned = _clone(func) +asmpython/_compiler/type_parameter_compat_fixes.py-271- parameter = cloned.params[parameter_index] +asmpython/_compiler/type_parameter_compat_fixes.py-272- cloned.name = clone_name +asmpython/_compiler/type_parameter_compat_fixes.py-273- del cloned.params[parameter_index] +asmpython/_compiler/type_parameter_compat_fixes.py-274- if parameter_index < len(cloned.defaults): +asmpython/_compiler/type_parameter_compat_fixes.py-275- del cloned.defaults[parameter_index] +asmpython/_compiler/type_parameter_compat_fixes.py-276- if parameter_index < len(cloned.param_types): +asmpython/_compiler/type_parameter_compat_fixes.py-277- del cloned.param_types[parameter_index] +asmpython/_compiler/type_parameter_compat_fixes.py-278- cloned.readonly_params = [ +asmpython/_compiler/type_parameter_compat_fixes.py-279- name for name in cloned.readonly_params if name != parameter +asmpython/_compiler/type_parameter_compat_fixes.py-280- ] +asmpython/_compiler/type_parameter_compat_fixes.py-281- if hasattr(func, "free_vars"): +asmpython/_compiler/type_parameter_compat_fixes.py-282- cloned.free_vars = [ +asmpython/_compiler/type_parameter_compat_fixes.py-283- name for name in getattr(func, "free_vars", []) if name != parameter +asmpython/_compiler/type_parameter_compat_fixes.py-284- ] +asmpython/_compiler/type_parameter_compat_fixes.py-285- if hasattr(func, "nonlocal_vars"): +asmpython/_compiler/type_parameter_compat_fixes.py-286- cloned.nonlocal_vars = [ +asmpython/_compiler/type_parameter_compat_fixes.py-287- name for name in getattr(func, "nonlocal_vars", []) if name != parameter +asmpython/_compiler/type_parameter_compat_fixes.py-288- ] +asmpython/_compiler/type_parameter_compat_fixes.py-289- _replace_parameter(cloned.body, parameter, class_name) +asmpython/_compiler/type_parameter_compat_fixes.py-290- if _returns_specialized_type(func, parameter): +asmpython/_compiler/type_parameter_compat_fixes.py-291- cloned.ret_type = (class_name, None) +asmpython/_compiler/type_parameter_compat_fixes.py-292- return cloned +asmpython/_compiler/type_parameter_compat_fixes.py-293- +asmpython/_compiler/type_parameter_compat_fixes.py-294- +asmpython/_compiler/type_parameter_compat_fixes.py-295-def _call_matches(call, is_method: bool, name: str) -> bool: +asmpython/_compiler/type_parameter_compat_fixes.py-296- if is_method: +asmpython/_compiler/type_parameter_compat_fixes.py:297: return isinstance(call, A.MethodCall) and call.method == name +asmpython/_compiler/type_parameter_compat_fixes.py-298- return isinstance(call, A.Call) and call.func == name +asmpython/_compiler/type_parameter_compat_fixes.py-299- +asmpython/_compiler/type_parameter_compat_fixes.py-300- +asmpython/_compiler/type_parameter_compat_fixes.py-301-def _safe_originals_to_neutralize(mod: A.Module, originals: dict) -> set: +asmpython/_compiler/type_parameter_compat_fixes.py-302- """Compute the greatest set whose only incoming calls come from that set. +asmpython/_compiler/type_parameter_compat_fixes.py-303- +asmpython/_compiler/type_parameter_compat_fixes.py-304- Calls between generic originals disappear together. Calls from module code, +asmpython/_compiler/type_parameter_compat_fixes.py-305- generated clones, or a generic original that must remain live keep their +asmpython/_compiler/type_parameter_compat_fixes.py-306- target live as well. Iterating removals computes the greatest safe set. +asmpython/_compiler/type_parameter_compat_fixes.py-307- """ +asmpython/_compiler/type_parameter_compat_fixes.py-308- candidates = set(originals) +asmpython/_compiler/type_parameter_compat_fixes.py-309- changed = True +asmpython/_compiler/type_parameter_compat_fixes.py-310- while changed: +asmpython/_compiler/type_parameter_compat_fixes.py-311- changed = False +asmpython/_compiler/type_parameter_compat_fixes.py-312- for original_id in list(candidates): +asmpython/_compiler/type_parameter_compat_fixes.py-313- _func, is_method, _owner_name, name = originals[original_id] +asmpython/_compiler/type_parameter_compat_fixes.py-314- externally_referenced = False +asmpython/_compiler/type_parameter_compat_fixes.py-315- for caller_id, call in _call_sites_with_owners(mod): +asmpython/_compiler/type_parameter_compat_fixes.py-316- if not _call_matches(call, is_method, name): +asmpython/_compiler/type_parameter_compat_fixes.py-317- continue +asmpython/_compiler/type_parameter_compat_fixes.py-318- if caller_id in candidates: +asmpython/_compiler/type_parameter_compat_fixes.py-319- continue +asmpython/_compiler/type_parameter_compat_fixes.py-320- externally_referenced = True +asmpython/_compiler/type_parameter_compat_fixes.py-321- break +asmpython/_compiler/type_parameter_compat_fixes.py-322- if externally_referenced: +asmpython/_compiler/type_parameter_compat_fixes.py-323- candidates.remove(original_id) +asmpython/_compiler/type_parameter_compat_fixes.py-324- changed = True +asmpython/_compiler/type_parameter_compat_fixes.py-325- return candidates +asmpython/_compiler/type_parameter_compat_fixes.py-326- +asmpython/_compiler/type_parameter_compat_fixes.py-327- +asmpython/_compiler/type_parameter_compat_fixes.py-328-def _lower_type_parameter_specializations(mod: A.Module) -> None: +asmpython/_compiler/type_parameter_compat_fixes.py-329- if getattr(mod, "_type_parameter_specializations_lowered", False): +asmpython/_compiler/type_parameter_compat_fixes.py-330- return +asmpython/_compiler/type_parameter_compat_fixes.py-331- mod._type_parameter_specializations_lowered = True +asmpython/_compiler/type_parameter_compat_fixes.py-332- +asmpython/_compiler/type_parameter_compat_fixes.py-333- class_names = {cls.name for cls in mod.classes} +asmpython/_compiler/type_parameter_compat_fixes.py-334- function_defs: dict = {} +asmpython/_compiler/type_parameter_compat_fixes.py-335- for func in mod.funcs: +asmpython/_compiler/type_parameter_compat_fixes.py-336- function_defs.setdefault(func.name, []).append(func) +asmpython/_compiler/type_parameter_compat_fixes.py-337- method_defs: dict = {} +asmpython/_compiler/type_parameter_compat_fixes.py-338- method_owners: dict = {} +asmpython/_compiler/type_parameter_compat_fixes.py-339- for cls in mod.classes: +asmpython/_compiler/type_parameter_compat_fixes.py-340- for method in cls.methods: +asmpython/_compiler/type_parameter_compat_fixes.py-341- method_defs.setdefault(method.name, []).append(method) +asmpython/_compiler/type_parameter_compat_fixes.py-342- method_owners.setdefault(method.name, []).append(cls) +asmpython/_compiler/type_parameter_compat_fixes.py-343- +asmpython/_compiler/type_parameter_compat_fixes.py-344- function_targets = { +asmpython/_compiler/type_parameter_compat_fixes.py-345- name: defs[0] +asmpython/_compiler/type_parameter_compat_fixes.py-346- for name, defs in function_defs.items() +asmpython/_compiler/type_parameter_compat_fixes.py-347- if len(defs) == 1 and _type_parameter_indices(defs[0], False) +asmpython/_compiler/type_parameter_compat_fixes.py-348- } +asmpython/_compiler/type_parameter_compat_fixes.py-349- method_targets = { +asmpython/_compiler/type_parameter_compat_fixes.py-350- name: defs[0] +asmpython/_compiler/type_parameter_compat_fixes.py-351- for name, defs in method_defs.items() +asmpython/_compiler/type_parameter_compat_fixes.py-352- if len(defs) == 1 and _type_parameter_indices(defs[0], True) +asmpython/_compiler/type_parameter_compat_fixes.py-353- } +asmpython/_compiler/type_parameter_compat_fixes.py-354- if not function_targets and not method_targets: +asmpython/_compiler/type_parameter_compat_fixes.py-355- return +asmpython/_compiler/type_parameter_compat_fixes.py-356- +asmpython/_compiler/type_parameter_compat_fixes.py-357- specializations: dict = {} +asmpython/_compiler/type_parameter_compat_fixes.py-358- changed = True +asmpython/_compiler/type_parameter_compat_fixes.py-359- while changed: +asmpython/_compiler/type_parameter_compat_fixes.py-360- changed = False +asmpython/_compiler/type_parameter_compat_fixes.py-361- for call in list(_all_call_nodes(mod)): +asmpython/_compiler/type_parameter_compat_fixes.py:362: is_method = isinstance(call, A.MethodCall) +asmpython/_compiler/type_parameter_compat_fixes.py-363- name = call.method if is_method else call.func +asmpython/_compiler/type_parameter_compat_fixes.py-364- func = method_targets.get(name) if is_method else function_targets.get(name) +asmpython/_compiler/type_parameter_compat_fixes.py-365- if func is None: +asmpython/_compiler/type_parameter_compat_fixes.py-366- continue +asmpython/_compiler/type_parameter_compat_fixes.py-367- indices = _type_parameter_indices(func, is_method) +asmpython/_compiler/type_parameter_compat_fixes.py-368- if not indices: +asmpython/_compiler/type_parameter_compat_fixes.py-369- continue +asmpython/_compiler/type_parameter_compat_fixes.py-370- parameter_index = indices[0] +asmpython/_compiler/type_parameter_compat_fixes.py-371- argument, binding = _argument_binding( +asmpython/_compiler/type_parameter_compat_fixes.py-372- call, +asmpython/_compiler/type_parameter_compat_fixes.py-373- func, +asmpython/_compiler/type_parameter_compat_fixes.py-374- parameter_index, +asmpython/_compiler/type_parameter_compat_fixes.py-375- is_method, +asmpython/_compiler/type_parameter_compat_fixes.py-376- ) +asmpython/_compiler/type_parameter_compat_fixes.py-377- if ( +asmpython/_compiler/type_parameter_compat_fixes.py-378- binding is None +asmpython/_compiler/type_parameter_compat_fixes.py-379- or not isinstance(argument, A.Name) +asmpython/_compiler/type_parameter_compat_fixes.py-380- or argument.name not in class_names +asmpython/_compiler/type_parameter_compat_fixes.py-381- ): +asmpython/_compiler/type_parameter_compat_fixes.py-382- continue +asmpython/_compiler/type_parameter_compat_fixes.py-383- +asmpython/_compiler/type_parameter_compat_fixes.py-384- class_name = argument.name +asmpython/_compiler/type_parameter_compat_fixes.py-385- owner_name = "" +asmpython/_compiler/type_parameter_compat_fixes.py-386- owner = None +asmpython/_compiler/type_parameter_compat_fixes.py-387- if is_method: From 6cb83dd73ba80df841ac5596795d0f5453dab614 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:47:05 -0500 Subject: [PATCH 03/53] Trigger provider runtime type verifier --- tests/cases/468_provider_type_runtime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 92fefcc5a..9ca7eb3df 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -3,6 +3,7 @@ # 0 # SomniaProvider # 1 +# Covers class-valued providers and unannotated dynamic parameters. class Provider: From 5887b0ea90dec044827c266975975dd10cbbd03b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:47:31 +0000 Subject: [PATCH 04/53] Record provider runtime compiler hooks --- provider-runtime-inspection.txt | 1278 ++++++++++++++----------------- 1 file changed, 576 insertions(+), 702 deletions(-) diff --git a/provider-runtime-inspection.txt b/provider-runtime-inspection.txt index b4e6d33a2..a2f5438a5 100644 --- a/provider-runtime-inspection.txt +++ b/provider-runtime-inspection.txt @@ -1,4 +1,9 @@ === resolve param types === +3360- if len(group) < 2: +3361- continue +3362- if not all("overload" in getattr(g, "decorators", []) for g in group): +3363- continue +3364- sigs: list = [] 3365- for g in group: 3366- r = self._resolve_annot(g.ret_type) 3367- sigs.append(FuncSig( @@ -132,7 +137,27 @@ 3495- lit = self._literal_arg_type(stmt.value) 3496- if lit is None: 3497- continue +3498- ty, el, val, tup = lit +3499- self._tuple_scan_globals.add( +3500- stmt.target, ty, el_type=el, value_type=val, tuple_types=tup +3501- ) +3502- +3503- # Infer which functions return a tuple, and the shape of that tuple, +3504- # so call sites can unpack `q, r = f()`. Done before body analysis so +3505- # forward references and recursion still see the inferred shape. +3506- for f in self.mod.funcs: +3507- f_body_str: list = f.body +3508- sig = self.funcs.get(f.name) +3509- ets = list(sig.ret_tuple) if sig is not None and sig.ret_tuple else self._scan_tuple_return(f_body_str) +3510- if ets is not None: +3511- self.func_ret_tuple[f.name] = ets +3512- -- +3688- if len(mgroup) < 2: +3689- continue +3690- if not all("overload" in getattr(g, "decorators", []) for g in mgroup): +3691- continue +3692- msigs: list = [] 3693- for g in mgroup: 3694- mr0 = self._resolve_annot(g.ret_type) 3695- msigs.append(FuncSig( @@ -179,7 +204,27 @@ 3736- sig.access[m.name] = "protected" 3737- if "final" in deco: 3738- sig.final_methods.add(m.name) +3739- if not (is_static or is_classm): +3740- if not m.params or m.params[0] != "self": +3741- raise SemaError( +3742- f"method {c.name}.{m.name!r} must take 'self' as its first parameter", +3743- m.pos, +3744- ErrorCode.E_MISSING_SELF_PARAM, +3745- ) +3746- mr = self._resolve_annot(m.ret_type) # type: ignore +3747- # `@x.setter` methods are registered under a mangled name +3748- # ("x__setter") so they don't collide in `methods`/codegen +3749- # symbols with the `@property` getter of the same name "x". +3750- # `ClassSig.setters` maps the property name to this mangled +3751- # name so `obj.x = v` can be rewritten to dispatch to it. +3752- setter_prop = None +3753- for d in getattr(m, "decorators", []): -- +4840- s_body: list = s.body +4841- self._collect_returns(s_body, acc) +4842- elif isinstance(s, A.Try): +4843- st_body: list = s.body +4844- st_handler: list = s.handler 4845- self._collect_returns(st_body, acc) 4846- self._collect_returns(st_handler, acc) 4847- st_extra: list = s.extra_handlers @@ -226,6 +271,21 @@ 4888- for a call site's `args`. Filters by arity match first (accounting 4889- for defaults, same as ordinary single-signature arity checking), 4890- then scores the arity-matching candidates by how many parameters +4891- have an EXACT static-type match against the call site's argument +4892- types (an unannotated "any" parameter matches anything but scores +4893- lower than a real match) -- the highest-scoring candidate wins; +4894- a tie or zero arity-matching candidates is an error. Deliberately +4895- simpler than full overload resolution (no covariance/promotion +4896- rules) -- a documented v1 simplification, not every C++/Java +4897- overload-resolution edge case. +4898- +4899- `implicit_self`: True for a method-form overload call, where +4900- `sig.arity`/`sig.param_types` include the `self` receiver +4901- (index 0) but the call-site `args` never do -- every comparison +4902- below is offset by 1 to account for that. +4903- """ +4904- offset = 1 if implicit_self else 0 +4905- n = len(args) 351- # `for row in rows: row[i]` recovers the leaf type. 352- ret_inner_el_type: object = None 353- # Parameter names and their default expressions (parallel to params, @@ -272,6 +332,11 @@ 394- 395- name: str 396- parent: Optional[str] +397- methods: dict[str, FuncSig] = field(default_factory=dict) +398- pos: A.SourcePos = None # type: ignore +399- # Field name -> static type ("int"/"str"/"float"/"list"/"dict"/"tuple"/ +400- # "instance:"), inferred from `self.x = ` assignments and +401- # `self.x: T` annotations. Drives the type of `obj.x` reads. Unknown fields -- 525- 526- @@ -319,6 +384,11 @@ 568- func_defs = {f.name: f for f in mod.funcs} 569- 570- needed_funcs: set = set() +571- needed_methods: set = set() +572- func_queue: list = [] +573- method_queue: list = [] +574- +575- def add_func(name) -> None: -- 790- # (mlang_objects), consumed by driver.py's link step. Populated by 791- # _inject_mlang_if_needed, consulted by A.MethodCall's `mlang:` @@ -398,6 +468,11 @@ 865- def _has_external_base(self, class_name: str) -> bool: 866- """True if `class_name` or any ancestor inherits from a base that isn't 867- a user-defined class (a builtin like Exception, or a name imported from +868- another module). Such a base may supply methods/fields asmpython can't +869- see, so member access against it is checked leniently.""" +870- cur = class_name +871- seen: set = set() +872- while cur is not None and cur not in seen: -- 1305- if el in ("list", "dict") and elval is not None: 1306- sig.field_inner_value_types[cname] = elval @@ -470,6 +545,11 @@ 1373- el = r[1] 1374- val = r[2] 1375- tup = r[3] +1376- elval = r[4] +1377- else: +1378- raw = self._static_value_info(s.value, pinfo) +1379- ty, el, val, tup = raw[0], raw[1], raw[2], raw[3] +1380- elval = None -- 1699- # same-named method with conflicting argument types, the 1700- # mismatch just falls back to `int` as before -- no new @@ -486,6 +566,46 @@ 1711- """Infer types for `fn`'s parameters at index >= `start` (0 for plain 1712- functions, 1 for methods to skip `self`) from `sites` (the `A.Call`/ 1713- `A.MethodCall` nodes invoking it), storing results in +1714: `self.inferred_param_types`. See `_infer_unannotated_params`.""" +1715: fn_param_types: list = fn.param_types +1716- fn_defaults: list = fn.defaults +1717- for i, p in enumerate(fn.params): +1718- if i < start: +1719- continue +1720: annot = fn_param_types[i] if i < len(fn_param_types) else None +1721- if self._resolve_annot(annot) is not None: # type: ignore +1722- continue +1723- if i < len(fn_defaults) and fn_defaults[i] is not None: +1724- continue +1725- candidates: list = [] +1726- found_any = False +1727- arg_idx = i - start +1728- for site in sites: +1729- args: list = site.args +1730- if arg_idx < len(args): +1731- arg = args[arg_idx] +1732- else: +1733- # Explicit loop, not next((v for n,v in ... if n==p), None): +1734- # asmpython's own codegen has no generator-expression/next() +1735- # support (this is the compiler's own source, self-compiled), +1736- # so that construct silently fell through to an unrelated +1737- # fallback and corrupted state instead of erroring. +1738- site_kwargs: list = site.kwargs +1739- arg = None +1740- for kw_name, kw_val in site_kwargs: +1741- if kw_name == p: +1742- arg = kw_val +1743- break +1744- if arg is None: +1745- continue +1746- lit = self._literal_arg_type(arg) +1747- if lit is None: +1748- continue +1749- found_any = True +1750- if lit not in candidates: +1751- candidates.append(lit) +1752- if found_any and len(candidates) == 1: +1753: self.inferred_param_types[f"{qualname}:{i}"] = candidates[0] === type method semantic branch === 8477- e.tuple_elem_types = self._dict_value_tuple_types(e.obj, scope) 8478- elif e.inferred_type == "dict": @@ -603,7 +723,22 @@ 8590- class_name = obj_t.split(":", 1)[1] 8591- ov_key = (class_name, e.method) 8592- if ov_key in self.method_overload_sets: -=== str builtin lowering === +8593- for a in e.args: +8594- self._check_expr(a, scope) +8595- ov_sig = self._resolve_overload( +8596- f"{class_name}.{e.method}", +8597- self.method_overload_sets[ov_key], +8598- e.args, +8599- e.pos, +8600- implicit_self=True, +8601- ) +8602- e.resolved_overload_symbol = _overload_symbol(e.method, ov_sig) +=== str builtin backend lowering === +asmpython/_compiler/sema.py-9916- e.tuple_elem_types = ["int", "int"] +asmpython/_compiler/sema.py-9917- return +asmpython/_compiler/sema.py-9918- # Argument-type sanity for builtins that care. An opaque ("any") +asmpython/_compiler/sema.py-9919- # argument is accepted everywhere — we can't know its real type. +asmpython/_compiler/sema.py-9920- if e.func == "len": asmpython/_compiler/sema.py-9921- t = A.expr_type(e.args[0]) asmpython/_compiler/sema.py-9922- if t not in ("str", "list", "dict", "tuple", "set", "any", "int") and not t.startswith("instance:"): asmpython/_compiler/sema.py-9923- # "int" is the default for unannotated vars — accept leniently @@ -670,7 +805,22 @@ asmpython/_compiler/sema.py-9983- ) asmpython/_compiler/sema.py-9984- raise SemaError( asmpython/_compiler/sema.py-9985- f"{e.func}() takes {required}-{sig.arity} arguments, got {len(e.args)}", asmpython/_compiler/sema.py-9986- e.pos, +asmpython/_compiler/sema.py-9987- ErrorCode.E_ARG_COUNT, +asmpython/_compiler/sema.py-9988- ) +asmpython/_compiler/sema.py-9989- # Normalize every call to a complete positional argument list +asmpython/_compiler/sema.py-9990- # (defaults filled, keyword args placed, varargs packed) so codegen +asmpython/_compiler/sema.py-9991- # always sees a fixed-shape call. +asmpython/_compiler/sema.py-9992- self._bind_args( +asmpython/_compiler/sema.py-9993- e, sig.param_names, sig.param_defaults, sig.vararg, e.pos, e.func, +asmpython/_compiler/sema.py-9994- kwarg=sig.kwarg, +asmpython/_compiler/sema.py-9995- ) +asmpython/_compiler/sema.py-9996- for a in e.args: -- +asmpython/_compiler/ir_lower.py-7462- # declares a float parameter (e.g. `sqrt(49)` -- a bare int +asmpython/_compiler/ir_lower.py-7463- # literal into a `("float",)`-typed binding): without this, +asmpython/_compiler/ir_lower.py-7464- # the raw integer bits get passed through unconverted and +asmpython/_compiler/ir_lower.py-7465- # reinterpreted as a double bit pattern on the callee side, +asmpython/_compiler/ir_lower.py-7466- # producing garbage (confirmed: `sqrt(49)` silently returned asmpython/_compiler/ir_lower.py-7467- # `0` instead of `7`). No reverse case needed -- a real asmpython/_compiler/ir_lower.py-7468- # Python float literal/expression passed to an int-typed asmpython/_compiler/ir_lower.py-7469- # parameter isn't valid input this compiler needs to handle @@ -737,7 +887,22 @@ asmpython/_compiler/ir_lower.py-7529- if isinstance(arg, A.StrLit): asmpython/_compiler/ir_lower.py-7530- s = arg.value.strip().lower() asmpython/_compiler/ir_lower.py-7531- if s == "nan": asmpython/_compiler/ir_lower.py-7532- out = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-7533- ctx.emit(IRInstr("const", out, [float("nan")])) +asmpython/_compiler/ir_lower.py-7534- return out +asmpython/_compiler/ir_lower.py-7535- if s in ("inf", "+inf", "infinity", "+infinity"): +asmpython/_compiler/ir_lower.py-7536- out = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-7537- ctx.emit(IRInstr("const", out, [float("inf")])) +asmpython/_compiler/ir_lower.py-7538- return out +asmpython/_compiler/ir_lower.py-7539- if s in ("-inf", "-infinity"): +asmpython/_compiler/ir_lower.py-7540- out = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-7541- ctx.emit(IRInstr("const", out, [float("-inf")])) +asmpython/_compiler/ir_lower.py-7542- return out -- +asmpython/_compiler/codegen.py-13673- f"mov rbx, [rbp{b_slot:+d}]", +asmpython/_compiler/codegen.py-13674- "call _runtime_range_list", +asmpython/_compiler/codegen.py-13675- ) +asmpython/_compiler/codegen.py-13676- return +asmpython/_compiler/codegen.py-13677- if e.func == "len": asmpython/_compiler/codegen.py-13678- arg = e.args[0] asmpython/_compiler/codegen.py-13679- self.gen_expr(arg, info) # rax = ptr asmpython/_compiler/codegen.py-13680- t: str = A.expr_type(arg) @@ -804,7 +969,17 @@ asmpython/_compiler/codegen.py-13740- self.emitf(f"mov [rbp{base_ asmpython/_compiler/codegen.py-13741- self.gen_expr(e.args[0], info) # rax = string ptr asmpython/_compiler/codegen.py-13742- self.emitf(f"mov rbx, [rbp{base_slot:+d}]") asmpython/_compiler/codegen.py-13743- self._emit_str_to_int_base() # rax = parsed int -=== class ids === +asmpython/_compiler/codegen.py-13744- return +asmpython/_compiler/codegen.py-13745- self.gen_expr(e.args[0], info) +asmpython/_compiler/codegen.py-13746- if arg_t == "str": +asmpython/_compiler/codegen.py-13747- self._emit_str_to_int() +asmpython/_compiler/codegen.py-13748- elif arg_t == "float": +asmpython/_compiler/codegen.py-13749- # Truncate toward zero (Python's int(float) semantics). +asmpython/_compiler/codegen.py-13750- self.emitf("cvttsd2si rax, xmm0") +asmpython/_compiler/codegen.py-13751- elif arg_t.startswith("instance:"): +asmpython/_compiler/codegen.py-13752- resolved = self._resolve_int_dunder(arg_t.split(":", 1)[1]) +asmpython/_compiler/codegen.py-13753- if resolved is not None: +=== class ids and type values === asmpython/_compiler/sema.py-212- "AttributeError", asmpython/_compiler/sema.py-213- "KeyError", asmpython/_compiler/sema.py-214- "IndexError", @@ -866,6 +1041,78 @@ asmpython/_compiler/sema.py-269- "*": ("__mul__", "__rmul__"), asmpython/_compiler/sema.py-270- "/": ("__truediv__", "__rtruediv__"), asmpython/_compiler/sema.py-271- "//": ("__floordiv__", "__rfloordiv__"), asmpython/_compiler/sema.py-272- "%": ("__mod__", "__rmod__"), +asmpython/_compiler/sema.py-273- "**": ("__pow__", "__rpow__"), +asmpython/_compiler/sema.py-274- "&": ("__and__", "__rand__"), +asmpython/_compiler/sema.py-275- "|": ("__or__", "__ror__"), +asmpython/_compiler/sema.py-276- "^": ("__xor__", "__rxor__"), +asmpython/_compiler/sema.py-277- "<<": ("__lshift__", "__rlshift__"), +-- +asmpython/_compiler/ir_lower.py-22-""" +asmpython/_compiler/ir_lower.py-23- +asmpython/_compiler/ir_lower.py-24-from __future__ import annotations +asmpython/_compiler/ir_lower.py-25- +asmpython/_compiler/ir_lower.py-26-from dataclasses import fields, is_dataclass +asmpython/_compiler/ir_lower.py-27- +asmpython/_compiler/ir_lower.py-28-from . import ast_nodes as A +asmpython/_compiler/ir_lower.py-29-from .ir import ( +asmpython/_compiler/ir_lower.py-30- IRBlock, +asmpython/_compiler/ir_lower.py-31- IRFunc, +asmpython/_compiler/ir_lower.py-32- IRGlobal, +asmpython/_compiler/ir_lower.py-33- IRInstr, +asmpython/_compiler/ir_lower.py-34- IRModule, +asmpython/_compiler/ir_lower.py-35- IRType, +asmpython/_compiler/ir_lower.py-36- IRValue, +asmpython/_compiler/ir_lower.py-37- F64, +asmpython/_compiler/ir_lower.py-38- I64, +asmpython/_compiler/ir_lower.py-39- PTR, +asmpython/_compiler/ir_lower.py-40- ir_type_for, +asmpython/_compiler/ir_lower.py-41-) +asmpython/_compiler/ir_lower.py:42:from .codegen import BUILTIN_EXC_IDS, BUILTIN_EXC_PARENTS, BUILTIN_TYPE_IDS, EXC_ANY +asmpython/_compiler/ir_lower.py-43- +asmpython/_compiler/ir_lower.py-44- +asmpython/_compiler/ir_lower.py-45-class LowerError(Exception): +asmpython/_compiler/ir_lower.py-46- pass +asmpython/_compiler/ir_lower.py-47- +asmpython/_compiler/ir_lower.py-48- +asmpython/_compiler/ir_lower.py-49-U8 = IRType("u8") +asmpython/_compiler/ir_lower.py-50- +asmpython/_compiler/ir_lower.py-51-# jmp_buf layout (mirrors _runtime_setjmp in codegen.py): rbx/rbp/r12-r15/ +asmpython/_compiler/ir_lower.py-52-# rsp/retaddr in the first 8 slots (0-56), rsi/rdi in slots 8-9 (64, 72) -- +asmpython/_compiler/ir_lower.py-53-# 10 regs * 8 bytes = 80 bytes. rsi/rdi were added after the first 8 slots +asmpython/_compiler/ir_lower.py-54-# were already established elsewhere; kept at the end rather than +asmpython/_compiler/ir_lower.py-55-# renumbering to avoid touching every other offset in this file. +asmpython/_compiler/ir_lower.py-56-_JMP_BUF_SIZE = 80 +asmpython/_compiler/ir_lower.py-57- +asmpython/_compiler/ir_lower.py-58-# Build parent-id map once at module level from BUILTIN_EXC_PARENTS. +asmpython/_compiler/ir_lower.py-59-_EXC_PARENT_OF: dict[int, int] = { +asmpython/_compiler/ir_lower.py-60- BUILTIN_EXC_IDS[k]: (BUILTIN_EXC_IDS[v] if v else -1) +asmpython/_compiler/ir_lower.py-61- for k, v in BUILTIN_EXC_PARENTS.items() +asmpython/_compiler/ir_lower.py-62- if k in BUILTIN_EXC_IDS +asmpython/_compiler/ir_lower.py-63-} +asmpython/_compiler/ir_lower.py-64- +asmpython/_compiler/ir_lower.py-65- +asmpython/_compiler/ir_lower.py-66-def _exc_raise_type_id_ir(value) -> int: +asmpython/_compiler/ir_lower.py-67- """Type id for `raise value` -- mirrors codegen._exc_raise_type_id.""" +asmpython/_compiler/ir_lower.py-68- name = None +asmpython/_compiler/ir_lower.py-69- if isinstance(value, A.Call): +asmpython/_compiler/ir_lower.py-70- name = value.func +asmpython/_compiler/ir_lower.py-71- elif isinstance(value, A.Name): +asmpython/_compiler/ir_lower.py-72- name = value.name +asmpython/_compiler/ir_lower.py-73- if name is not None and name in BUILTIN_EXC_IDS: +asmpython/_compiler/ir_lower.py-74- return BUILTIN_EXC_IDS[name] +asmpython/_compiler/ir_lower.py-75- return EXC_ANY +asmpython/_compiler/ir_lower.py-76- +asmpython/_compiler/ir_lower.py-77- +asmpython/_compiler/ir_lower.py-78-def _exc_matching_ids_ir(types: list) -> list: +asmpython/_compiler/ir_lower.py-79- """Full set of type ids an `except (T1, T2, ...):` catches, +asmpython/_compiler/ir_lower.py-80- including subtypes and EXC_ANY -- mirrors codegen._exc_matching_ids.""" +asmpython/_compiler/ir_lower.py-81- ancestor_ids = [BUILTIN_EXC_IDS[t] for t in types if t in BUILTIN_EXC_IDS] +asmpython/_compiler/ir_lower.py-82- matches: list = [EXC_ANY] +asmpython/_compiler/ir_lower.py-83- for a in ancestor_ids: +asmpython/_compiler/ir_lower.py-84- if a not in matches: +asmpython/_compiler/ir_lower.py-85- matches.append(a) +asmpython/_compiler/ir_lower.py-86- for tid in BUILTIN_EXC_IDS.values(): +asmpython/_compiler/ir_lower.py-87- for a in ancestor_ids: -- asmpython/_compiler/ir_lower.py-134- # to a compile-time-known scalar value (stdlib.Const), NOT a real asmpython/_compiler/ir_lower.py-135- # runtime global. `_lower_expr`'s A.Name case checks this BEFORE @@ -928,6 +1175,11 @@ asmpython/_compiler/ir_lower.py-191- return name asmpython/_compiler/ir_lower.py-192- asmpython/_compiler/ir_lower.py-193- asmpython/_compiler/ir_lower.py-194-class _FuncCtx: +asmpython/_compiler/ir_lower.py-195- def __init__( +asmpython/_compiler/ir_lower.py-196- self, +asmpython/_compiler/ir_lower.py-197- mctx: _ModuleCtx, +asmpython/_compiler/ir_lower.py-198- *, +asmpython/_compiler/ir_lower.py-199- local_names: set[str] | None = None, -- asmpython/_compiler/ir_lower.py-2212- return "staticmethod" in getattr(msig, "decorators", []) asmpython/_compiler/ir_lower.py-2213- @@ -1001,6 +1253,11 @@ asmpython/_compiler/ir_lower.py-2280- return _value_repr_kind(t) asmpython/_compiler/ir_lower.py-2281- asmpython/_compiler/ir_lower.py-2282- asmpython/_compiler/ir_lower.py-2283-def _list_repr_kind(e: A.Expr) -> int: +asmpython/_compiler/ir_lower.py-2284- el = getattr(e, "list_el_type", "int") or "int" +asmpython/_compiler/ir_lower.py-2285- inner = getattr(e, "list_el_value_type", "int") or "int" +asmpython/_compiler/ir_lower.py-2286- if isinstance(e, A.ListLit): +asmpython/_compiler/ir_lower.py-2287- el = e.el_type or "int" +asmpython/_compiler/ir_lower.py-2288- inner = getattr(e, "el_value_type", "int") or "int" -- asmpython/_compiler/ir_lower.py-3786- arg0_t = A.expr_type(arg0) asmpython/_compiler/ir_lower.py-3787- has_prim_target = False @@ -1063,6 +1320,11 @@ asmpython/_compiler/ir_lower.py-3843- ctx.emit(IRInstr("ior", next_v, [ma asmpython/_compiler/ir_lower.py-3844- match_v = next_v asmpython/_compiler/ir_lower.py-3845- ctx.emit(IRInstr("store", None, [match_v, out_ptr])) asmpython/_compiler/ir_lower.py-3846- ctx.emit(IRInstr("br", None, [end_b.label])) +asmpython/_compiler/ir_lower.py-3847- +asmpython/_compiler/ir_lower.py-3848- ctx.switch_to(end_b) +asmpython/_compiler/ir_lower.py-3849- out = ctx.tmp(I64) +asmpython/_compiler/ir_lower.py-3850- ctx.emit(IRInstr("load", out, [out_ptr])) +asmpython/_compiler/ir_lower.py-3851- return out -- asmpython/_compiler/ir_lower.py-4460- _emit_instance_field_set(ctx, obj_v, "_alive", zero_v) asmpython/_compiler/ir_lower.py-4461- return obj_v @@ -1088,9 +1350,9 @@ asmpython/_compiler/ir_lower.py:4480: if e.name in ctx.mctx.class_ids: asmpython/_compiler/ir_lower.py-4481- v = ctx.tmp(I64) asmpython/_compiler/ir_lower.py:4482: ctx.emit(IRInstr("const", v, [ctx.mctx.class_ids[e.name]])) asmpython/_compiler/ir_lower.py-4483- return v -asmpython/_compiler/ir_lower.py-4484- if e.name in BUILTIN_TYPE_IDS: +asmpython/_compiler/ir_lower.py:4484: if e.name in BUILTIN_TYPE_IDS: asmpython/_compiler/ir_lower.py-4485- v = ctx.tmp(I64) -asmpython/_compiler/ir_lower.py-4486- ctx.emit(IRInstr("const", v, [BUILTIN_TYPE_IDS[e.name]])) +asmpython/_compiler/ir_lower.py:4486: ctx.emit(IRInstr("const", v, [BUILTIN_TYPE_IDS[e.name]])) asmpython/_compiler/ir_lower.py-4487- return v asmpython/_compiler/ir_lower.py-4488- if e.name in ctx.mctx.ffi_consts and e.name not in ctx.slot_ty: asmpython/_compiler/ir_lower.py-4489- # `from math import pi` -- a bare name bound to a stdlib.Const, @@ -1127,6 +1389,15 @@ asmpython/_compiler/ir_lower.py-4519- return v asmpython/_compiler/ir_lower.py-4520- if b.ty == "int" and isinstance(value, (int, bool)): asmpython/_compiler/ir_lower.py-4521- v = ctx.tmp(I64) asmpython/_compiler/ir_lower.py-4522- ctx.emit(IRInstr("const", v, [int(value)])) +asmpython/_compiler/ir_lower.py-4523- return v +asmpython/_compiler/ir_lower.py-4524- if b.ty == "float" and isinstance(value, (int, float)): +asmpython/_compiler/ir_lower.py-4525- v = ctx.tmp(F64) +asmpython/_compiler/ir_lower.py-4526- ctx.emit(IRInstr("const", v, [float(value)])) +asmpython/_compiler/ir_lower.py-4527- return v +asmpython/_compiler/ir_lower.py-4528- # Anything else (list-typed constants, etc.) falls through to +asmpython/_compiler/ir_lower.py-4529- # the generic path unchanged -- a separate, smaller, +asmpython/_compiler/ir_lower.py-4530- # not-yet-scoped gap, same as the module.CONST case's own +asmpython/_compiler/ir_lower.py-4531- # matching fallthrough note. -- asmpython/_compiler/ir_lower.py-7403- ctx.emit(IRInstr("const", zero, [0])) asmpython/_compiler/ir_lower.py-7404- v = ctx.tmp(I64) @@ -1189,6 +1460,11 @@ asmpython/_compiler/ir_lower.py-7460- av = _lower_expr(ctx, a) asmpython/_compiler/ir_lower.py-7461- # Coerce an int-typed argument to float when the binding asmpython/_compiler/ir_lower.py-7462- # declares a float parameter (e.g. `sqrt(49)` -- a bare int asmpython/_compiler/ir_lower.py-7463- # literal into a `("float",)`-typed binding): without this, +asmpython/_compiler/ir_lower.py-7464- # the raw integer bits get passed through unconverted and +asmpython/_compiler/ir_lower.py-7465- # reinterpreted as a double bit pattern on the callee side, +asmpython/_compiler/ir_lower.py-7466- # producing garbage (confirmed: `sqrt(49)` silently returned +asmpython/_compiler/ir_lower.py-7467- # `0` instead of `7`). No reverse case needed -- a real +asmpython/_compiler/ir_lower.py-7468- # Python float literal/expression passed to an int-typed -- asmpython/_compiler/ir_lower.py-9352- def add_resolved(class_name: str, method: str) -> None: asmpython/_compiler/ir_lower.py-9353- add(_resolve_method_owner_in_sigs(classes_sig, class_name, method), method) @@ -1251,6 +1527,11 @@ asmpython/_compiler/ir_lower.py-9409- # already handles the None- asmpython/_compiler/ir_lower.py-9410- # case as a no-op. asmpython/_compiler/ir_lower.py-9411- add_resolved(obj_ty.split(":", 1)[1], node.method) asmpython/_compiler/ir_lower.py-9412- elif obj_ty == "type" and isinstance(node.obj, A.Name): +asmpython/_compiler/ir_lower.py-9413- add_resolved(node.obj.name, node.method) +asmpython/_compiler/ir_lower.py-9414- # `node.obj` (the receiver) is visited (or deliberately +asmpython/_compiler/ir_lower.py-9415- # skipped, for a bare Name) once, uniformly for both +asmpython/_compiler/ir_lower.py-9416- # MethodCall and Attr, in the generic dataclass-field +asmpython/_compiler/ir_lower.py-9417- # recursion below -- see its own comment for why. -- asmpython/_compiler/codegen.py-68- "ZeroDivisionError": 6, asmpython/_compiler/codegen.py-69- "OverflowError": 7, @@ -1276,7 +1557,7 @@ asmpython/_compiler/codegen.py:88:# mimicking argparse's `type=str`) -- same RTT asmpython/_compiler/codegen.py-89-# BUILTIN_EXC_IDS: asmpython has no first-class type objects, so this is just asmpython/_compiler/codegen.py-90-# a stable, unique-per-name placeholder the program never actually inspects. asmpython/_compiler/codegen.py:91:# Negative so it can never collide with class_ids (which starts at 0). -asmpython/_compiler/codegen.py-92-BUILTIN_TYPE_IDS: dict[str, int] = { +asmpython/_compiler/codegen.py:92:BUILTIN_TYPE_IDS: dict[str, int] = { asmpython/_compiler/codegen.py-93- "int": -1, asmpython/_compiler/codegen.py-94- "float": -2, asmpython/_compiler/codegen.py-95- "str": -3, @@ -1305,704 +1586,297 @@ asmpython/_compiler/codegen.py-117- frame_size: int = 0 # bytes to subtract asmpython/_compiler/codegen.py-118- # Default value expressions, one per param (None for required). asmpython/_compiler/codegen.py-119- defaults: list = field(default_factory=list) asmpython/_compiler/codegen.py-120- # Running RBP offset (negative) used while collecting locals, and whether -=== MethodCall codegen === -asmpython/_compiler/program.py-331- if name == "asyncio": -asmpython/_compiler/program.py-332- return 1 -asmpython/_compiler/program.py-333- if name == "importlib": -asmpython/_compiler/program.py-334- return 1 -asmpython/_compiler/program.py-335- return 0 -asmpython/_compiler/program.py-336- -asmpython/_compiler/program.py-337- -asmpython/_compiler/program.py-338-def _flatten_targets(targets: list, out: set[str]) -> None: -asmpython/_compiler/program.py-339- """Collect every name bound by a (possibly nested) unpack target list, -asmpython/_compiler/program.py-340- e.g. `["a", ["b", "c"]]` -> {"a", "b", "c"}. Mirrors sema's -asmpython/_compiler/program.py-341- `_flat_target_names` for the subset program.py needs.""" -asmpython/_compiler/program.py-342- for t in targets: -asmpython/_compiler/program.py-343- if isinstance(t, str): -asmpython/_compiler/program.py-344- out.add(t) -asmpython/_compiler/program.py-345- elif isinstance(t, list): -asmpython/_compiler/program.py-346- _flatten_targets(t, out) -asmpython/_compiler/program.py-347- -asmpython/_compiler/program.py-348- -asmpython/_compiler/program.py-349-def _free_names(node: object, out: set[str]) -> None: -asmpython/_compiler/program.py-350- """Collect the bare names an expression references: `Name` lookups and -asmpython/_compiler/program.py:351: `Call`/`MethodCall` callee names. Used to decide whether a value-import's -asmpython/_compiler/program.py-352- initializer can be safely materialized (every name it needs must already -asmpython/_compiler/program.py-353- be available). Attribute names and string literals are not free -asmpython/_compiler/program.py-354- variables, so they're skipped. Explicit per-node-type walk over every -asmpython/_compiler/program.py-355- expression shape (no statement shapes: every call site passes a single -asmpython/_compiler/program.py-356- expression, e.g. an import initializer or an `if`/assert test). -asmpython/_compiler/program.py-357- """ -asmpython/_compiler/program.py-358- if node is None: -asmpython/_compiler/program.py-359- return -asmpython/_compiler/program.py-360- if isinstance(node, A.Name): -asmpython/_compiler/program.py-361- out.add(node.name) -asmpython/_compiler/program.py-362- return -asmpython/_compiler/program.py-363- if isinstance(node, A.Call): -asmpython/_compiler/program.py-364- out.add(node.func) -asmpython/_compiler/program.py-365- for a in node.args: -asmpython/_compiler/program.py-366- _free_names(a, out) -asmpython/_compiler/program.py-367- for _kw, val in node.kwargs: -asmpython/_compiler/program.py-368- _free_names(val, out) -asmpython/_compiler/program.py-369- return -asmpython/_compiler/program.py:370: if isinstance(node, A.MethodCall): -asmpython/_compiler/program.py-371- # `obj.method(...)`: the receiver and args are sub-expressions; the -asmpython/_compiler/program.py-372- # method name itself is an attribute, not a free variable. -asmpython/_compiler/program.py-373- _free_names(node.obj, out) -asmpython/_compiler/program.py-374- for a in node.args: -asmpython/_compiler/program.py-375- _free_names(a, out) -asmpython/_compiler/program.py-376- for _kw, val in node.kwargs: -asmpython/_compiler/program.py-377- _free_names(val, out) -asmpython/_compiler/program.py-378- return -asmpython/_compiler/program.py-379- if isinstance(node, A.Attr): -asmpython/_compiler/program.py-380- # `obj.name`: only the object is a free reference. -asmpython/_compiler/program.py-381- _free_names(node.obj, out) -asmpython/_compiler/program.py-382- return -asmpython/_compiler/program.py-383- if isinstance(node, A.Comprehension): -asmpython/_compiler/program.py-384- # `[elt for a, b in iter if cond]`: `var`/`targets` are loop-bound -asmpython/_compiler/program.py-385- # names, not free references — collect names from the rest of the -asmpython/_compiler/program.py-386- # node (elt/key/value/iter/cond/extra_for_*) and drop the bound -asmpython/_compiler/program.py-387- # ones, so e.g. `{fwd for fwd, _rfl in DUNDER_BINOP.values()}` -asmpython/_compiler/program.py-388- # reports only `DUNDER_BINOP` as free, not `fwd`/`_rfl`. -asmpython/_compiler/program.py-389- _nc: A.Comprehension = node -asmpython/_compiler/program.py-390- bound: set[str] = set() -asmpython/_compiler/program.py-391- if _nc.var: -asmpython/_compiler/program.py-392- bound.add(_nc.var) -asmpython/_compiler/program.py-393- _flatten_targets(_nc.targets, bound) -asmpython/_compiler/program.py-394- for t in _nc.extra_for_vars: -asmpython/_compiler/program.py-395- if t: -asmpython/_compiler/program.py-396- bound.add(t) -asmpython/_compiler/program.py-397- for t in _nc.extra_for_targets: -asmpython/_compiler/program.py-398- _flatten_targets(t, bound) -asmpython/_compiler/program.py-399- inner: set[str] = set() -asmpython/_compiler/program.py-400- _free_names(_nc.elt, inner) -asmpython/_compiler/program.py-401- _free_names(_nc.iter, inner) -asmpython/_compiler/program.py-402- if _nc.cond is not None: -asmpython/_compiler/program.py-403- _free_names(_nc.cond, inner) -asmpython/_compiler/program.py-404- for ei in _nc.extra_for_iters: -asmpython/_compiler/program.py-405- _free_names(ei, inner) -asmpython/_compiler/program.py-406- for ec in _nc.extra_for_conds: -asmpython/_compiler/program.py-407- if ec is not None: -asmpython/_compiler/program.py-408- _free_names(ec, inner) -asmpython/_compiler/program.py-409- out |= inner - bound -asmpython/_compiler/program.py-410- return -asmpython/_compiler/program.py-411- if isinstance(node, A.DictComprehension): -asmpython/_compiler/program.py-412- # Unlike A.Comprehension, DictComprehension has no extra_for_* -asmpython/_compiler/program.py-413- # fields — it only supports a single `for` clause. -asmpython/_compiler/program.py-414- _ndc: A.DictComprehension = node -asmpython/_compiler/program.py-415- bound2: set[str] = set() -asmpython/_compiler/program.py-416- if _ndc.var: -asmpython/_compiler/program.py-417- bound2.add(_ndc.var) -asmpython/_compiler/program.py-418- _flatten_targets(_ndc.targets, bound2) -asmpython/_compiler/program.py-419- inner2: set[str] = set() -asmpython/_compiler/program.py-420- _free_names(_ndc.key, inner2) -asmpython/_compiler/program.py-421- _free_names(_ndc.value, inner2) -asmpython/_compiler/program.py-422- _free_names(_ndc.iter, inner2) -asmpython/_compiler/program.py-423- if _ndc.cond is not None: -asmpython/_compiler/program.py-424- _free_names(_ndc.cond, inner2) -asmpython/_compiler/program.py-425- out |= inner2 - bound2 -asmpython/_compiler/program.py-426- return -asmpython/_compiler/program.py-427- if isinstance(node, A.BinOp): -asmpython/_compiler/program.py-428- _free_names(node.left, out) -asmpython/_compiler/program.py-429- _free_names(node.right, out) -asmpython/_compiler/program.py-430- return --- -asmpython/_compiler/program.py-1031- elif isinstance(s, A.YieldStmt): -asmpython/_compiler/program.py-1032- _rename_call_targets_expr(s.value, renames) -asmpython/_compiler/program.py-1033- elif isinstance(s, A.Match): -asmpython/_compiler/program.py-1034- _rename_call_targets_expr(s.subject, renames) -asmpython/_compiler/program.py-1035- for _pattern, guard, body in s.cases: -asmpython/_compiler/program.py-1036- if guard is not None: -asmpython/_compiler/program.py-1037- _rename_call_targets_expr(guard, renames) -asmpython/_compiler/program.py-1038- _rename_call_targets(body, renames) -asmpython/_compiler/program.py-1039- elif isinstance(s, A.ClosureBind) and s.func_name in renames: -asmpython/_compiler/program.py-1040- s.func_name = renames[s.func_name] -asmpython/_compiler/program.py-1041- -asmpython/_compiler/program.py-1042- -asmpython/_compiler/program.py-1043-def _rename_call_targets_expr(e, renames: dict[str, str]) -> None: -asmpython/_compiler/program.py-1044- if isinstance(e, A.Call): -asmpython/_compiler/program.py-1045- if e.func in renames: -asmpython/_compiler/program.py-1046- e.func = renames[e.func] -asmpython/_compiler/program.py-1047- for a in e.args: -asmpython/_compiler/program.py-1048- _rename_call_targets_expr(a, renames) -asmpython/_compiler/program.py-1049- for _kn, kv in e.kwargs: -asmpython/_compiler/program.py-1050- _rename_call_targets_expr(kv, renames) -asmpython/_compiler/program.py:1051: elif isinstance(e, A.MethodCall): -asmpython/_compiler/program.py-1052- _rename_call_targets_expr(e.obj, renames) -asmpython/_compiler/program.py-1053- for a in e.args: -asmpython/_compiler/program.py-1054- _rename_call_targets_expr(a, renames) -asmpython/_compiler/program.py-1055- for _kn, kv in e.kwargs: -asmpython/_compiler/program.py-1056- _rename_call_targets_expr(kv, renames) -asmpython/_compiler/program.py-1057- elif isinstance(e, A.BinOp): -asmpython/_compiler/program.py-1058- _rename_call_targets_expr(e.left, renames) -asmpython/_compiler/program.py-1059- _rename_call_targets_expr(e.right, renames) -asmpython/_compiler/program.py-1060- elif isinstance(e, A.UnaryOp): -asmpython/_compiler/program.py-1061- _rename_call_targets_expr(e.operand, renames) -asmpython/_compiler/program.py-1062- elif isinstance(e, A.Compare): -asmpython/_compiler/program.py-1063- for o in e.operands: -asmpython/_compiler/program.py-1064- _rename_call_targets_expr(o, renames) -asmpython/_compiler/program.py-1065- elif isinstance(e, A.BoolOp): -asmpython/_compiler/program.py-1066- _rename_call_targets_expr(e.left, renames) -asmpython/_compiler/program.py-1067- _rename_call_targets_expr(e.right, renames) -asmpython/_compiler/program.py-1068- elif isinstance(e, A.IfExp): -asmpython/_compiler/program.py-1069- _rename_call_targets_expr(e.test, renames) -asmpython/_compiler/program.py-1070- _rename_call_targets_expr(e.body, renames) -asmpython/_compiler/program.py-1071- _rename_call_targets_expr(e.orelse, renames) -asmpython/_compiler/program.py-1072- elif isinstance(e, A.NamedExpr): -asmpython/_compiler/program.py-1073- _rename_call_targets_expr(e.value, renames) -asmpython/_compiler/program.py-1074- elif isinstance(e, A.ListLit): -asmpython/_compiler/program.py-1075- for el in e.elems: -asmpython/_compiler/program.py-1076- _rename_call_targets_expr(el, renames) -asmpython/_compiler/program.py-1077- elif isinstance(e, A.Subscript): -asmpython/_compiler/program.py-1078- _rename_call_targets_expr(e.obj, renames) -asmpython/_compiler/program.py-1079- if isinstance(e.index, A.Slice): -asmpython/_compiler/program.py-1080- if e.index.start is not None: -asmpython/_compiler/program.py-1081- _rename_call_targets_expr(e.index.start, renames) -asmpython/_compiler/program.py-1082- if e.index.stop is not None: -asmpython/_compiler/program.py-1083- _rename_call_targets_expr(e.index.stop, renames) -asmpython/_compiler/program.py-1084- if e.index.step is not None: -asmpython/_compiler/program.py-1085- _rename_call_targets_expr(e.index.step, renames) -asmpython/_compiler/program.py-1086- else: -asmpython/_compiler/program.py-1087- _rename_call_targets_expr(e.index, renames) -asmpython/_compiler/program.py-1088- elif isinstance(e, A.Attr): -asmpython/_compiler/program.py-1089- _rename_call_targets_expr(e.obj, renames) -asmpython/_compiler/program.py-1090- elif isinstance(e, A.FString): -asmpython/_compiler/program.py-1091- for seg in e.segments: -asmpython/_compiler/program.py-1092- _rename_call_targets_expr(seg, renames) -asmpython/_compiler/program.py-1093- elif isinstance(e, A.DictLit): -asmpython/_compiler/program.py-1094- for k in e.keys: -asmpython/_compiler/program.py-1095- if k is not None: -asmpython/_compiler/program.py-1096- _rename_call_targets_expr(k, renames) -asmpython/_compiler/program.py-1097- for v in e.values: -asmpython/_compiler/program.py-1098- _rename_call_targets_expr(v, renames) -asmpython/_compiler/program.py-1099- elif isinstance(e, A.TupleLit): -asmpython/_compiler/program.py-1100- for el in e.elems: -asmpython/_compiler/program.py-1101- _rename_call_targets_expr(el, renames) -asmpython/_compiler/program.py-1102- elif isinstance(e, A.SetLit): -asmpython/_compiler/program.py-1103- for el in e.elems: -asmpython/_compiler/program.py-1104- _rename_call_targets_expr(el, renames) -asmpython/_compiler/program.py-1105- elif isinstance(e, A.Starred): -asmpython/_compiler/program.py-1106- _rename_call_targets_expr(e.value, renames) -asmpython/_compiler/program.py-1107- elif isinstance(e, A.Comprehension): -asmpython/_compiler/program.py-1108- _rename_call_targets_expr(e.elt, renames) -asmpython/_compiler/program.py-1109- _rename_call_targets_expr(e.iter, renames) -asmpython/_compiler/program.py-1110- if e.cond is not None: -asmpython/_compiler/program.py-1111- _rename_call_targets_expr(e.cond, renames) --- -asmpython/_compiler/parser.py-199- # clause(s) — Python scopes these to the comprehension itself, not -asmpython/_compiler/parser.py-200- # the surrounding function, so a `Name` read while this is non-empty -asmpython/_compiler/parser.py-201- # must not be recorded as a free-var reference. A list-as-stack -asmpython/_compiler/parser.py-202- # (rather than reassigning a set) so nested comprehensions compose -asmpython/_compiler/parser.py-203- # without needing `nonlocal`. -asmpython/_compiler/parser.py-204- comp_suppressed: list = [] -asmpython/_compiler/parser.py-205- def _collect_refs_expr(node) -> None: -asmpython/_compiler/parser.py-206- if isinstance(node, A.Name): -asmpython/_compiler/parser.py-207- if node.name not in comp_suppressed: -asmpython/_compiler/parser.py-208- referenced.add(node.name) -asmpython/_compiler/parser.py-209- elif isinstance(node, A.BinOp): -asmpython/_compiler/parser.py-210- _collect_refs_expr(node.left) -asmpython/_compiler/parser.py-211- _collect_refs_expr(node.right) -asmpython/_compiler/parser.py-212- elif isinstance(node, A.UnaryOp): -asmpython/_compiler/parser.py-213- _collect_refs_expr(node.operand) -asmpython/_compiler/parser.py-214- elif isinstance(node, A.Call): -asmpython/_compiler/parser.py-215- for a in node.args: -asmpython/_compiler/parser.py-216- _collect_refs_expr(a) -asmpython/_compiler/parser.py-217- for _kw_name, kw_val in (node.kwargs or []): -asmpython/_compiler/parser.py-218- _collect_refs_expr(kw_val) -asmpython/_compiler/parser.py:219: elif isinstance(node, A.MethodCall): -asmpython/_compiler/parser.py-220- _collect_refs_expr(node.obj) -asmpython/_compiler/parser.py-221- for a in node.args: -asmpython/_compiler/parser.py-222- _collect_refs_expr(a) -asmpython/_compiler/parser.py-223- for _kw_name, kw_val in (node.kwargs or []): -asmpython/_compiler/parser.py-224- _collect_refs_expr(kw_val) -asmpython/_compiler/parser.py-225- elif isinstance(node, A.Attr): -asmpython/_compiler/parser.py-226- _collect_refs_expr(node.obj) -asmpython/_compiler/parser.py-227- elif isinstance(node, A.Subscript): -asmpython/_compiler/parser.py-228- _collect_refs_expr(node.obj) -asmpython/_compiler/parser.py-229- _collect_refs_expr(node.index) -asmpython/_compiler/parser.py-230- elif isinstance(node, A.Slice): -asmpython/_compiler/parser.py-231- if node.start is not None: -asmpython/_compiler/parser.py-232- _collect_refs_expr(node.start) -asmpython/_compiler/parser.py-233- if node.stop is not None: -asmpython/_compiler/parser.py-234- _collect_refs_expr(node.stop) -asmpython/_compiler/parser.py-235- if node.step is not None: -asmpython/_compiler/parser.py-236- _collect_refs_expr(node.step) -asmpython/_compiler/parser.py-237- elif isinstance(node, A.IfExp): -asmpython/_compiler/parser.py-238- _collect_refs_expr(node.test) -asmpython/_compiler/parser.py-239- _collect_refs_expr(node.body) -asmpython/_compiler/parser.py-240- _collect_refs_expr(node.orelse) -asmpython/_compiler/parser.py-241- elif isinstance(node, A.NamedExpr): -asmpython/_compiler/parser.py-242- _collect_refs_expr(node.value) -asmpython/_compiler/parser.py-243- elif isinstance(node, A.BoolOp): -asmpython/_compiler/parser.py-244- _collect_refs_expr(node.left) -asmpython/_compiler/parser.py-245- _collect_refs_expr(node.right) -asmpython/_compiler/parser.py-246- elif isinstance(node, A.Compare): -asmpython/_compiler/parser.py-247- for op in node.operands: -asmpython/_compiler/parser.py-248- _collect_refs_expr(op) -asmpython/_compiler/parser.py-249- elif isinstance(node, A.ListLit): -asmpython/_compiler/parser.py-250- for e in node.elems: -asmpython/_compiler/parser.py-251- _collect_refs_expr(e) -asmpython/_compiler/parser.py-252- elif isinstance(node, A.TupleLit): -asmpython/_compiler/parser.py-253- for e in node.elems: -asmpython/_compiler/parser.py-254- _collect_refs_expr(e) -asmpython/_compiler/parser.py-255- elif isinstance(node, A.SetLit): -asmpython/_compiler/parser.py-256- for e in node.elems: -asmpython/_compiler/parser.py-257- _collect_refs_expr(e) -asmpython/_compiler/parser.py-258- elif isinstance(node, A.DictLit): -asmpython/_compiler/parser.py-259- for k in node.keys: -asmpython/_compiler/parser.py-260- if k is not None: -asmpython/_compiler/parser.py-261- _collect_refs_expr(k) -asmpython/_compiler/parser.py-262- for v in node.values: -asmpython/_compiler/parser.py-263- _collect_refs_expr(v) -asmpython/_compiler/parser.py-264- elif isinstance(node, A.FString): -asmpython/_compiler/parser.py-265- for seg in node.segments: -asmpython/_compiler/parser.py-266- _collect_refs_expr(seg) -asmpython/_compiler/parser.py-267- elif isinstance(node, A.Starred): -asmpython/_compiler/parser.py-268- _collect_refs_expr(node.value) -asmpython/_compiler/parser.py-269- elif isinstance(node, A.Lambda): -asmpython/_compiler/parser.py-270- if node.body is not None: -asmpython/_compiler/parser.py-271- _collect_refs_expr(node.body) -asmpython/_compiler/parser.py-272- elif isinstance(node, A.Comprehension): -asmpython/_compiler/parser.py-273- # `iter` (the outermost `for x in `) runs in the -asmpython/_compiler/parser.py-274- # *enclosing* scope in real Python, so walk it before any -asmpython/_compiler/parser.py-275- # suppression is pushed. -asmpython/_compiler/parser.py-276- _collect_refs_expr(node.iter) -asmpython/_compiler/parser.py-277- _comp_vars: list = [] -asmpython/_compiler/parser.py-278- if node.var: -asmpython/_compiler/parser.py-279- _comp_vars.append(node.var) --- -asmpython/_compiler/parser.py-512- Parser._collect_called_names_expr(s.value, out) -asmpython/_compiler/parser.py-513- elif isinstance(s, A.Del): -asmpython/_compiler/parser.py-514- Parser._collect_called_names_expr(s.target, out) -asmpython/_compiler/parser.py-515- elif isinstance(s, A.YieldStmt): -asmpython/_compiler/parser.py-516- Parser._collect_called_names_expr(s.value, out) -asmpython/_compiler/parser.py-517- elif isinstance(s, A.Match): -asmpython/_compiler/parser.py-518- Parser._collect_called_names_expr(s.subject, out) -asmpython/_compiler/parser.py-519- for _pattern, guard, body in s.cases: -asmpython/_compiler/parser.py-520- if guard is not None: -asmpython/_compiler/parser.py-521- Parser._collect_called_names_expr(guard, out) -asmpython/_compiler/parser.py-522- Parser._collect_called_names(body, out) -asmpython/_compiler/parser.py-523- -asmpython/_compiler/parser.py-524- @staticmethod -asmpython/_compiler/parser.py-525- def _collect_called_names_expr(e, out: set) -> None: -asmpython/_compiler/parser.py-526- if isinstance(e, A.Call): -asmpython/_compiler/parser.py-527- out.add(e.func) -asmpython/_compiler/parser.py-528- for a in e.args: -asmpython/_compiler/parser.py-529- Parser._collect_called_names_expr(a, out) -asmpython/_compiler/parser.py-530- for _kn, kv in e.kwargs: -asmpython/_compiler/parser.py-531- Parser._collect_called_names_expr(kv, out) -asmpython/_compiler/parser.py:532: elif isinstance(e, A.MethodCall): -asmpython/_compiler/parser.py-533- Parser._collect_called_names_expr(e.obj, out) -asmpython/_compiler/parser.py-534- for a in e.args: -asmpython/_compiler/parser.py-535- Parser._collect_called_names_expr(a, out) -asmpython/_compiler/parser.py-536- for _kn, kv in e.kwargs: -asmpython/_compiler/parser.py-537- Parser._collect_called_names_expr(kv, out) -asmpython/_compiler/parser.py-538- elif isinstance(e, A.BinOp): -asmpython/_compiler/parser.py-539- Parser._collect_called_names_expr(e.left, out) -asmpython/_compiler/parser.py-540- Parser._collect_called_names_expr(e.right, out) -asmpython/_compiler/parser.py-541- elif isinstance(e, A.UnaryOp): -asmpython/_compiler/parser.py-542- Parser._collect_called_names_expr(e.operand, out) -asmpython/_compiler/parser.py-543- elif isinstance(e, A.Compare): -asmpython/_compiler/parser.py-544- for o in e.operands: -asmpython/_compiler/parser.py-545- Parser._collect_called_names_expr(o, out) -asmpython/_compiler/parser.py-546- elif isinstance(e, A.BoolOp): -asmpython/_compiler/parser.py-547- Parser._collect_called_names_expr(e.left, out) -asmpython/_compiler/parser.py-548- Parser._collect_called_names_expr(e.right, out) -asmpython/_compiler/parser.py-549- elif isinstance(e, A.IfExp): -asmpython/_compiler/parser.py-550- Parser._collect_called_names_expr(e.test, out) -asmpython/_compiler/parser.py-551- Parser._collect_called_names_expr(e.body, out) -asmpython/_compiler/parser.py-552- Parser._collect_called_names_expr(e.orelse, out) -asmpython/_compiler/parser.py-553- elif isinstance(e, A.NamedExpr): -asmpython/_compiler/parser.py-554- Parser._collect_called_names_expr(e.value, out) -asmpython/_compiler/parser.py-555- elif isinstance(e, A.ListLit): -asmpython/_compiler/parser.py-556- for el in e.elems: -asmpython/_compiler/parser.py-557- Parser._collect_called_names_expr(el, out) -asmpython/_compiler/parser.py-558- elif isinstance(e, A.Subscript): -asmpython/_compiler/parser.py-559- Parser._collect_called_names_expr(e.obj, out) -asmpython/_compiler/parser.py-560- if isinstance(e.index, A.Slice): -asmpython/_compiler/parser.py-561- if e.index.start is not None: -asmpython/_compiler/parser.py-562- Parser._collect_called_names_expr(e.index.start, out) -asmpython/_compiler/parser.py-563- if e.index.stop is not None: -asmpython/_compiler/parser.py-564- Parser._collect_called_names_expr(e.index.stop, out) -asmpython/_compiler/parser.py-565- if e.index.step is not None: -asmpython/_compiler/parser.py-566- Parser._collect_called_names_expr(e.index.step, out) -asmpython/_compiler/parser.py-567- else: -asmpython/_compiler/parser.py-568- Parser._collect_called_names_expr(e.index, out) -asmpython/_compiler/parser.py-569- elif isinstance(e, A.Attr): -asmpython/_compiler/parser.py-570- Parser._collect_called_names_expr(e.obj, out) -asmpython/_compiler/parser.py-571- elif isinstance(e, A.FString): -asmpython/_compiler/parser.py-572- for seg in e.segments: -asmpython/_compiler/parser.py-573- Parser._collect_called_names_expr(seg, out) -asmpython/_compiler/parser.py-574- elif isinstance(e, A.DictLit): -asmpython/_compiler/parser.py-575- for k in e.keys: -asmpython/_compiler/parser.py-576- if k is not None: -asmpython/_compiler/parser.py-577- Parser._collect_called_names_expr(k, out) -asmpython/_compiler/parser.py-578- for v in e.values: -asmpython/_compiler/parser.py-579- Parser._collect_called_names_expr(v, out) -asmpython/_compiler/parser.py-580- elif isinstance(e, A.TupleLit): -asmpython/_compiler/parser.py-581- for el in e.elems: -asmpython/_compiler/parser.py-582- Parser._collect_called_names_expr(el, out) -asmpython/_compiler/parser.py-583- elif isinstance(e, A.SetLit): -asmpython/_compiler/parser.py-584- for el in e.elems: -asmpython/_compiler/parser.py-585- Parser._collect_called_names_expr(el, out) -asmpython/_compiler/parser.py-586- elif isinstance(e, A.Starred): -asmpython/_compiler/parser.py-587- Parser._collect_called_names_expr(e.value, out) -asmpython/_compiler/parser.py-588- elif isinstance(e, A.Comprehension): -asmpython/_compiler/parser.py-589- Parser._collect_called_names_expr(e.elt, out) -asmpython/_compiler/parser.py-590- Parser._collect_called_names_expr(e.iter, out) -asmpython/_compiler/parser.py-591- if e.cond is not None: -asmpython/_compiler/parser.py-592- Parser._collect_called_names_expr(e.cond, out) --- -asmpython/_compiler/parser.py-3090- if not self._check("OP", ":") and not self._check("OP", "]"): -asmpython/_compiler/parser.py-3091- stop = self._parse_expr() -asmpython/_compiler/parser.py-3092- if self._check("OP", ":"): -asmpython/_compiler/parser.py-3093- self._eat() -asmpython/_compiler/parser.py-3094- if not self._check("OP", "]"): -asmpython/_compiler/parser.py-3095- step = self._parse_expr() -asmpython/_compiler/parser.py-3096- self._expect("OP", "]") -asmpython/_compiler/parser.py-3097- idx = A.Slice(start=start, stop=stop, step=step, pos=lbr.pos) -asmpython/_compiler/parser.py-3098- else: -asmpython/_compiler/parser.py-3099- self._expect("OP", "]") -asmpython/_compiler/parser.py-3100- idx = start -asmpython/_compiler/parser.py-3101- atom = A.Subscript(obj=atom, index=idx, pos=lbr.pos) # type: ignore -asmpython/_compiler/parser.py-3102- elif self._check("OP", "."): -asmpython/_compiler/parser.py-3103- dot = self._eat() -asmpython/_compiler/parser.py-3104- name = self._expect("NAME").value -asmpython/_compiler/parser.py-3105- if self._check("OP", "("): -asmpython/_compiler/parser.py-3106- # obj.name(...) — method call -asmpython/_compiler/parser.py-3107- self._eat() -asmpython/_compiler/parser.py-3108- args, kwargs = self._parse_call_args() # type: ignore -asmpython/_compiler/parser.py-3109- self._expect("OP", ")") -asmpython/_compiler/parser.py:3110: atom = A.MethodCall( -asmpython/_compiler/parser.py-3111- obj=atom, -asmpython/_compiler/parser.py-3112- method=name, # type: ignore -asmpython/_compiler/parser.py-3113- args=args, -asmpython/_compiler/parser.py-3114- kwargs=kwargs, -asmpython/_compiler/parser.py-3115- pos=dot.pos, # type: ignore -asmpython/_compiler/parser.py-3116- ) -asmpython/_compiler/parser.py-3117- else: -asmpython/_compiler/parser.py-3118- # obj.name — attribute access (e.g. math.pi) -asmpython/_compiler/parser.py-3119- atom = A.Attr(obj=atom, name=name, pos=dot.pos) # type: ignore -asmpython/_compiler/parser.py-3120- else: -asmpython/_compiler/parser.py-3121- return atom -asmpython/_compiler/parser.py-3122- -asmpython/_compiler/parser.py-3123- def _parse_paren_or_tuple(self): -asmpython/_compiler/parser.py-3124- """After a '(': parse either a parenthesised expression or a tuple. -asmpython/_compiler/parser.py-3125- -asmpython/_compiler/parser.py-3126- A comma is what makes it a tuple: -asmpython/_compiler/parser.py-3127- () -> empty tuple -asmpython/_compiler/parser.py-3128- (a) -> just `a` (grouping, not a tuple) -asmpython/_compiler/parser.py-3129- (a,) -> 1-tuple -asmpython/_compiler/parser.py-3130- (a, b, c) -> 3-tuple (trailing comma allowed) -asmpython/_compiler/parser.py-3131- """ -asmpython/_compiler/parser.py-3132- lpar = self._expect("OP", "(") -asmpython/_compiler/parser.py-3133- if self._check("OP", ")"): -asmpython/_compiler/parser.py-3134- self._eat() -asmpython/_compiler/parser.py-3135- return A.TupleLit(elems=[], pos=lpar.pos) -asmpython/_compiler/parser.py-3136- tuple_has_star = False -asmpython/_compiler/parser.py-3137- if self._check("OP", "*"): -asmpython/_compiler/parser.py-3138- star_pos = self._eat().pos -asmpython/_compiler/parser.py-3139- first = A.Starred(value=self._parse_expr(), pos=star_pos) -asmpython/_compiler/parser.py-3140- tuple_has_star = True -asmpython/_compiler/parser.py-3141- else: -asmpython/_compiler/parser.py-3142- first = self._parse_expr() -asmpython/_compiler/parser.py-3143- if self._check("KEYWORD", "for"): -asmpython/_compiler/parser.py-3144- if isinstance(first, A.Starred): -asmpython/_compiler/parser.py-3145- raise ParseError( -asmpython/_compiler/parser.py-3146- "generator expression element cannot be starred", -asmpython/_compiler/parser.py-3147- first.pos, -asmpython/_compiler/parser.py-3148- ) -asmpython/_compiler/parser.py-3149- comp = self._parse_comprehension_tail(first, lpar.pos) -asmpython/_compiler/parser.py-3150- self._expect("OP", ")") -asmpython/_compiler/parser.py-3151- return comp -asmpython/_compiler/parser.py-3152- if not self._check("OP", ","): -asmpython/_compiler/parser.py-3153- self._expect("OP", ")") -asmpython/_compiler/parser.py-3154- return first -asmpython/_compiler/parser.py-3155- elems = [first] -asmpython/_compiler/parser.py-3156- while self._check("OP", ","): -asmpython/_compiler/parser.py-3157- self._eat() -asmpython/_compiler/parser.py-3158- if self._check("OP", ")"): -asmpython/_compiler/parser.py-3159- break # trailing comma -asmpython/_compiler/parser.py-3160- if self._check("OP", "*"): -asmpython/_compiler/parser.py-3161- star_pos2 = self._eat().pos -asmpython/_compiler/parser.py-3162- elems.append(A.Starred(value=self._parse_expr(), pos=star_pos2)) -asmpython/_compiler/parser.py-3163- tuple_has_star = True -asmpython/_compiler/parser.py-3164- else: -asmpython/_compiler/parser.py-3165- elems.append(self._parse_expr()) -asmpython/_compiler/parser.py-3166- self._expect("OP", ")") -asmpython/_compiler/parser.py-3167- if tuple_has_star: -asmpython/_compiler/parser.py-3168- return A.ListLit(elems=elems, pos=lpar.pos) -asmpython/_compiler/parser.py-3169- return A.TupleLit(elems=elems, pos=lpar.pos) -asmpython/_compiler/parser.py-3170- +asmpython/_compiler/codegen.py-121- # this is the synthetic module-entry frame. Carried on the FuncInfo so the +asmpython/_compiler/codegen.py-122- # local-collection helpers can be plain instance methods (no closures / +asmpython/_compiler/codegen.py-123- # nonlocal) — a self-host requirement. +asmpython/_compiler/codegen.py-124- offset: int = 0 +asmpython/_compiler/codegen.py-125- is_main: bool = False +asmpython/_compiler/codegen.py-126- # Names declared `global` in this function: skip frame-slot allocation and +asmpython/_compiler/codegen.py-127- # access them via the module-global .bss slot instead. +asmpython/_compiler/codegen.py-128- global_names: set = field(default_factory=set) +asmpython/_compiler/codegen.py-129- # Nonlocal vars: maps var name -> box-ptr slot name (__nl_box_). +asmpython/_compiler/codegen.py-130- # Reads/writes to these names go through one pointer indirection. +asmpython/_compiler/codegen.py-131- nonlocal_boxes: dict = field(default_factory=dict) +asmpython/_compiler/codegen.py-132- # True when the function's declared return annotation is `-> float`. +asmpython/_compiler/codegen.py-133- # Lets `return ` promote a non-float result (e.g. an `any`/`int` +asmpython/_compiler/codegen.py-134- # element read out of an unannotated `list`) to xmm0, since callers of a +asmpython/_compiler/codegen.py-135- # float-returning function read the result from xmm0. +asmpython/_compiler/codegen.py-136- ret_is_float: bool = False +asmpython/_compiler/codegen.py-137- -- +asmpython/_compiler/codegen.py-288- and isinstance(s.target, str) +asmpython/_compiler/codegen.py-289- and s.target not in bound_in_frame +asmpython/_compiler/codegen.py-290- ): +asmpython/_compiler/codegen.py-291- self.global_vars[s.target] = A.expr_type(s.value) +asmpython/_compiler/codegen.py-292- elif ( +asmpython/_compiler/codegen.py-293- isinstance(s, A.ConstDecl) +asmpython/_compiler/codegen.py-294- and s.name not in bound_in_frame +asmpython/_compiler/codegen.py-295- ): +asmpython/_compiler/codegen.py-296- # const NAME = value is module-scope-only (enforced by the +asmpython/_compiler/codegen.py-297- # parser), so it's always a global slot, never a frame local +asmpython/_compiler/codegen.py-298- # -- same collection as a plain top-level A.Assign. +asmpython/_compiler/codegen.py-299- self.global_vars[s.name] = A.expr_type(s.value) +asmpython/_compiler/codegen.py-300- elif isinstance(s, A.If): +asmpython/_compiler/codegen.py-301- self._collect_if_globals(s, bound_in_frame, self.global_vars) +asmpython/_compiler/codegen.py-302- # RTTI: each user class gets a small integer id. Instances are tagged +asmpython/_compiler/codegen.py-303- # with their id (a hidden `__class__` dict entry) at construction, and +asmpython/_compiler/codegen.py-304- # isinstance walks the `__class_parents` table to honour inheritance. +asmpython/_compiler/codegen.py-305- # Built with an explicit loop rather than a dict comprehension over +asmpython/_compiler/codegen.py-306- # enumerate(...) so this stays self-compilable (asmpython comprehensions +asmpython/_compiler/codegen.py-307- # don't take enumerate() iterables or tuple targets). +asmpython/_compiler/codegen.py:308: self.class_ids: dict[str, int] = {} +asmpython/_compiler/codegen.py-309- cid = 0 +asmpython/_compiler/codegen.py-310- for cls in mod.classes: +asmpython/_compiler/codegen.py:311: self.class_ids[cls.name] = cid +asmpython/_compiler/codegen.py-312- cid += 1 +asmpython/_compiler/codegen.py-313- # Lazily-built .rodata table mapping each class id above to a +asmpython/_compiler/codegen.py-314- # "" string, for type(instance) (see +asmpython/_compiler/codegen.py-315- # _type_name_table_label). None until first requested. +asmpython/_compiler/codegen.py-316- self.type_name_table: list[str] | None = None +asmpython/_compiler/codegen.py-317- # Class-level variables that act as static constants: `class C: x = 5`. +asmpython/_compiler/codegen.py-318- # Each maps "." -> (label, default_expr). Emitted as bss +asmpython/_compiler/codegen.py-319- # globals, initialized at startup, and read/written via ClassName.attr +asmpython/_compiler/codegen.py-320- # and cls.attr. Only plain class bodies (not @dataclass, whose class +asmpython/_compiler/codegen.py-321- # vars are per-instance fields) contribute, and only literal/simple +asmpython/_compiler/codegen.py-322- # defaults that the startup initializer can evaluate. +asmpython/_compiler/codegen.py-323- self.class_var_labels: dict[str, str] = {} +asmpython/_compiler/codegen.py-324- self.class_var_defaults: list = [] # (label, default_expr) in emit order +asmpython/_compiler/codegen.py-325- for cls in mod.classes: +asmpython/_compiler/codegen.py-326- if getattr(cls, "is_dataclass", False): +asmpython/_compiler/codegen.py-327- continue +asmpython/_compiler/codegen.py-328- for cv in getattr(cls, "class_vars", []) or []: +asmpython/_compiler/codegen.py-329- cvname, _annot, cvdefault = cv +asmpython/_compiler/codegen.py-330- if cvdefault is None: +asmpython/_compiler/codegen.py-331- continue +asmpython/_compiler/codegen.py-332- label = f"__cv_{cls.name}__{cvname}" +asmpython/_compiler/codegen.py-333- self.class_var_labels[f"{cls.name}.{cvname}"] = label +asmpython/_compiler/codegen.py-334- self.class_var_defaults.append((label, cvdefault)) +asmpython/_compiler/codegen.py-335- # import_binary()/.imported dynamic-loading: map each handle variable +asmpython/_compiler/codegen.py-336- # name to the list of (func_name, FuncDef) decorated `@handle.imported` +asmpython/_compiler/codegen.py-337- # for it. A handle's import_binary(path) call site resolves every +asmpython/_compiler/codegen.py-338- # function in its list via GetProcAddress/dlsym immediately, storing +asmpython/_compiler/codegen.py-339- # each pointer keyed by name on the handle instance — see +asmpython/_compiler/codegen.py-340- # _gen_constructor-adjacent dynamic-import codegen. +asmpython/_compiler/codegen.py-341- # +asmpython/_compiler/codegen.py-342- # Also scans class methods (mod.classes[*].methods), not just +asmpython/_compiler/codegen.py-343- # top-level mod.funcs: a class can wrap a set of GL bindings behind +asmpython/_compiler/codegen.py-344- # its own API (e.g. pugtk's GLRenderer3D) instead of forcing every +asmpython/_compiler/codegen.py-345- # caller to hand-declare the same ~20 top-level @glfns.imported +asmpython/_compiler/codegen.py-346- # stubs. `handle` (the `@.imported` decorator's receiver) +asmpython/_compiler/codegen.py-347- # still has to be a name resolvable where the decorator itself is +asmpython/_compiler/codegen.py-348- # evaluated -- a module-level `glfns = gl_import()`, since Python +asmpython/_compiler/codegen.py-349- # evaluates a class's decorators once, at class-definition time, +asmpython/_compiler/codegen.py-350- # not per-instance. Methods compile with `self` as their first +asmpython/_compiler/codegen.py-351- # parameter; _gen_dynamic_call's plain_params helper below strips +asmpython/_compiler/codegen.py-352- # it before marshalling so `self` is never sent to the GL call. +asmpython/_compiler/codegen.py-353- self.imported_funcs: dict[str, list[tuple[str, A.FuncDef]]] = {} +asmpython/_compiler/codegen.py-354- for f in mod.funcs: +asmpython/_compiler/codegen.py-355- for deco in f.decorators: +asmpython/_compiler/codegen.py-356- if deco.endswith(".imported"): -- +asmpython/_compiler/codegen.py-635- +asmpython/_compiler/codegen.py-636- def intern_string(self, s: str) -> tuple[str, int]: +asmpython/_compiler/codegen.py-637- """Add a string to .data, return (label, byte_length). +asmpython/_compiler/codegen.py-638- +asmpython/_compiler/codegen.py-639- Emits the string as a comma-separated list of byte values for NASM. +asmpython/_compiler/codegen.py-640- We walk the characters and take `ord(ch)` rather than `s.encode()` so +asmpython/_compiler/codegen.py-641- this stays self-compilable (asmpython has no bytes type). For the ASCII +asmpython/_compiler/codegen.py-642- source the compiler emits this is exact; `ord` on a 1-char asmpython str +asmpython/_compiler/codegen.py-643- already yields its byte value. +asmpython/_compiler/codegen.py-644- """ +asmpython/_compiler/codegen.py-645- label = f"str_{len(self.strings)}" +asmpython/_compiler/codegen.py-646- parts: list = [] +asmpython/_compiler/codegen.py-647- for ch in s: +asmpython/_compiler/codegen.py-648- parts.append(str(ord(ch))) +asmpython/_compiler/codegen.py-649- body = ",".join(parts) if parts else "0" +asmpython/_compiler/codegen.py-650- self.strings.append((label, body)) +asmpython/_compiler/codegen.py-651- return label, len(parts) +asmpython/_compiler/codegen.py-652- +asmpython/_compiler/codegen.py-653- def _type_name_table_label(self) -> str: +asmpython/_compiler/codegen.py-654- """Lazily build a .rodata table mapping each user class's RTTI id +asmpython/_compiler/codegen.py:655: (see self.class_ids) to a "" string, so +asmpython/_compiler/codegen.py-656- type(instance) can index into it by runtime class id.""" +asmpython/_compiler/codegen.py-657- if self.type_name_table is None: +asmpython/_compiler/codegen.py-658- table = [] +asmpython/_compiler/codegen.py-659- _tnt_i = 0 +asmpython/_compiler/codegen.py:660: while _tnt_i < len(self.class_ids): +asmpython/_compiler/codegen.py-661- table.append("") +asmpython/_compiler/codegen.py-662- _tnt_i += 1 +asmpython/_compiler/codegen.py:663: for name, cid in self.class_ids.items(): +asmpython/_compiler/codegen.py-664- label, _ = self.intern_string(f"") +asmpython/_compiler/codegen.py-665- table[cid] = label +asmpython/_compiler/codegen.py-666- self.type_name_table = table +asmpython/_compiler/codegen.py-667- return "__type_name_table" +asmpython/_compiler/codegen.py-668- +asmpython/_compiler/codegen.py-669- # ---- driver ------------------------------------------------------------- +asmpython/_compiler/codegen.py-670- +asmpython/_compiler/codegen.py-671- def generate(self) -> str: +asmpython/_compiler/codegen.py-672- self.emit(f"; asmpython generated for target = {self.target_name}") +asmpython/_compiler/codegen.py-673- self.emit("BITS 64") +asmpython/_compiler/codegen.py-674- self.emit("default rel") +asmpython/_compiler/codegen.py-675- before = len(self.lines) +asmpython/_compiler/codegen.py-676- self.emit_externs() +asmpython/_compiler/codegen.py-677- # Avoid duplicate `extern foo` declarations: the target subclass +asmpython/_compiler/codegen.py-678- # already emits some. Collect their symbol names (the last token of each +asmpython/_compiler/codegen.py-679- # `extern ...` line). Built as a list with an explicit loop rather than +asmpython/_compiler/codegen.py-680- # a set comprehension so this stays self-compilable (asmpython has no +asmpython/_compiler/codegen.py-681- # set runtime); `already` is only used for membership below. +asmpython/_compiler/codegen.py-682- already: list = [] +asmpython/_compiler/codegen.py-683- for line in self.lines[before:]: +asmpython/_compiler/codegen.py-684- stripped = line.strip() +asmpython/_compiler/codegen.py-685- if stripped.startswith("extern"): +asmpython/_compiler/codegen.py-686- already.append(stripped.split()[-1]) +asmpython/_compiler/codegen.py-687- # Symbols defined inline by emit_asmlib_runtime must not also be +asmpython/_compiler/codegen.py-688- # declared `extern` — that would conflict with their label definition. +asmpython/_compiler/codegen.py-689- inline = self._asmlib_inline_syms() +asmpython/_compiler/codegen.py-690- for sym in sorted(self.ffi_externs): +asmpython/_compiler/codegen.py-691- if sym not in already and sym not in inline: +asmpython/_compiler/codegen.py-692- self.emit(f"extern {sym}") +asmpython/_compiler/codegen.py-693- self.emit(self.section_text) +asmpython/_compiler/codegen.py-694- self.emit_entry() +asmpython/_compiler/codegen.py-695- # Whole-program compilation (program.py's load_program) merges EVERY +asmpython/_compiler/codegen.py-696- # function/method from every imported stdlib module unconditionally, +asmpython/_compiler/codegen.py-697- # whether or not the program actually calls it -- sema.py tolerates +asmpython/_compiler/codegen.py-698- # (doesn't hard-fail on) a semantic error in one of these if it's +asmpython/_compiler/codegen.py-699- # unreachable, so a name that's merged-but-broken can reach this +asmpython/_compiler/codegen.py-700- # point at all. The x86-64 backend (ir_lower.py) already prunes these +asmpython/_compiler/codegen.py-701- # via `_reachable_callables` before lowering; do the same filtering +asmpython/_compiler/codegen.py-702- # here so the legacy backend doesn't try to emit a call into a +asmpython/_compiler/codegen.py-703- # skipped, potentially-half-checked body (e.g. a reference to the +asmpython/_compiler/codegen.py-704- # undefined symbol `property`, from collections.py's `namedtuple()`) +asmpython/_compiler/codegen.py-705- # for code that never runs. Reused as-is: it's a pure function of the +asmpython/_compiler/codegen.py-706- # already-fully-typed `self.mod` with no IR-specific state. +asmpython/_compiler/codegen.py-707- from .ir_lower import _reachable_callables +asmpython/_compiler/codegen.py-708- -- +asmpython/_compiler/codegen.py-2214- self._cl_define(info, f"__dm_new_{id(expr)}") +asmpython/_compiler/codegen.py-2215- if obj_t == "set" and expr.method in ( +asmpython/_compiler/codegen.py-2216- "union", +asmpython/_compiler/codegen.py-2217- "intersection", +asmpython/_compiler/codegen.py-2218- "difference", +asmpython/_compiler/codegen.py-2219- ): +asmpython/_compiler/codegen.py-2220- self._cl_define(info, f"__sm_other_{id(expr)}") +asmpython/_compiler/codegen.py-2221- self._cl_define(info, f"__sm_new_{id(expr)}") +asmpython/_compiler/codegen.py-2222- self._cl_define(info, f"__sm_keys_{id(expr)}") +asmpython/_compiler/codegen.py-2223- self._cl_define(info, f"__sm_idx_{id(expr)}") +asmpython/_compiler/codegen.py-2224- self._cl_define(info, f"__sm_key_{id(expr)}") +asmpython/_compiler/codegen.py-2225- if obj_t.startswith("instance:"): +asmpython/_compiler/codegen.py-2226- self._cl_define(info, f"__callself_{id(expr)}") +asmpython/_compiler/codegen.py-2227- # Module-qualified call to a merged project function +asmpython/_compiler/codegen.py-2228- # (`A.expr_type(x)`) lowers to a plain call, so it needs the +asmpython/_compiler/codegen.py-2229- # same per-arg slots as an instance/super call. +asmpython/_compiler/codegen.py-2230- is_module_fn = obj_t == "module" and expr.method in self.funcs +asmpython/_compiler/codegen.py-2231- # `ClassName.staticmethod(args)` / `.classmethod(args)`: a plain +asmpython/_compiler/codegen.py-2232- # call to the method symbol, so it needs per-arg slots too. +asmpython/_compiler/codegen.py-2233- is_class_static = ( +asmpython/_compiler/codegen.py:2234: isinstance(expr.obj, A.Name) and expr.obj.name in self.class_ids +asmpython/_compiler/codegen.py-2235- ) +asmpython/_compiler/codegen.py-2236- if ( +asmpython/_compiler/codegen.py-2237- obj_t.startswith("instance:") +asmpython/_compiler/codegen.py-2238- or obj_t.startswith("super:") +asmpython/_compiler/codegen.py-2239- or is_module_fn +asmpython/_compiler/codegen.py-2240- or is_class_static +asmpython/_compiler/codegen.py-2241- ): +asmpython/_compiler/codegen.py-2242- for k in range(len(expr.args)): +asmpython/_compiler/codegen.py-2243- self._cl_define(info, f"__callarg_{id(expr)}_{k}") +asmpython/_compiler/codegen.py-2244- self._cl_walk_expr(info, expr.obj) +asmpython/_compiler/codegen.py-2245- for a in expr.args: +asmpython/_compiler/codegen.py-2246- self._cl_walk_expr(info, a) +asmpython/_compiler/codegen.py-2247- for _kn, kv in getattr(expr, "kwargs", []) or []: +asmpython/_compiler/codegen.py-2248- self._cl_walk_expr(info, kv) +asmpython/_compiler/codegen.py-2249- elif isinstance(expr, A.Subscript): +asmpython/_compiler/codegen.py-2250- if isinstance(expr.index, A.Slice): +asmpython/_compiler/codegen.py-2251- # slice needs scratch slots for obj/start (always) plus +asmpython/_compiler/codegen.py-2252- # stop/step when the step-aware path is used. +asmpython/_compiler/codegen.py-2253- self._cl_define(info, f"__strsl_obj_{id(expr)}") +asmpython/_compiler/codegen.py-2254- self._cl_define(info, f"__strsl_start_{id(expr)}") +asmpython/_compiler/codegen.py-2255- if expr.index.step is not None: +asmpython/_compiler/codegen.py-2256- self._cl_define(info, f"__strsl_stop_{id(expr)}") +asmpython/_compiler/codegen.py-2257- self._cl_define(info, f"__strsl_step_{id(expr)}") +asmpython/_compiler/codegen.py-2258- # List slicing dispatches through a different helper; the +asmpython/_compiler/codegen.py-2259- # codegen path uses its own pair of slots. +asmpython/_compiler/codegen.py-2260- if A.expr_type(expr.obj) == "list": +asmpython/_compiler/codegen.py-2261- self._cl_define(info, f"__lstsl_obj_{id(expr)}") +asmpython/_compiler/codegen.py-2262- self._cl_define(info, f"__lstsl_start_{id(expr)}") +asmpython/_compiler/codegen.py-2263- if expr.index.step is not None: +asmpython/_compiler/codegen.py-2264- self._cl_define(info, f"__lstsl_step_{id(expr)}") +asmpython/_compiler/codegen.py-2265- self._cl_walk_expr(info, expr.obj) +asmpython/_compiler/codegen.py-2266- if expr.index.start is not None: +asmpython/_compiler/codegen.py-2267- self._cl_walk_expr(info, expr.index.start) +asmpython/_compiler/codegen.py-2268- if expr.index.stop is not None: +asmpython/_compiler/codegen.py-2269- self._cl_walk_expr(info, expr.index.stop) +asmpython/_compiler/codegen.py-2270- if expr.index.step is not None: +asmpython/_compiler/codegen.py-2271- self._cl_walk_expr(info, expr.index.step) +asmpython/_compiler/codegen.py-2272- elif A.expr_type(expr.obj) == "str": +asmpython/_compiler/codegen.py-2273- self._cl_define(info, f"__stridx_{id(expr)}") +asmpython/_compiler/codegen.py-2274- self._cl_walk_expr(info, expr.obj) +asmpython/_compiler/codegen.py-2275- self._cl_walk_expr(info, expr.index) +asmpython/_compiler/codegen.py-2276- elif getattr(expr, "_getitem_class", None) is not None: +asmpython/_compiler/codegen.py-2277- # Instance __getitem__: synthesized method call needs receiver +asmpython/_compiler/codegen.py-2278- # slot and one arg slot (the index). We park them under +asmpython/_compiler/codegen.py-2279- # __gi_self_ / __gi_arg_ so they don't collide with -- -asmpython/_compiler/type_parameter_compat_fixes.py-120- return result -asmpython/_compiler/type_parameter_compat_fixes.py-121- -asmpython/_compiler/type_parameter_compat_fixes.py-122- -asmpython/_compiler/type_parameter_compat_fixes.py-123-def _assignment_related_variables(func, parameter: str) -> set: -asmpython/_compiler/type_parameter_compat_fixes.py-124- related = set() -asmpython/_compiler/type_parameter_compat_fixes.py-125- changed = True -asmpython/_compiler/type_parameter_compat_fixes.py-126- while changed: -asmpython/_compiler/type_parameter_compat_fixes.py-127- changed = False -asmpython/_compiler/type_parameter_compat_fixes.py-128- for node in _walk_stmts(func.body): -asmpython/_compiler/type_parameter_compat_fixes.py-129- if not isinstance(node, A.Assign): -asmpython/_compiler/type_parameter_compat_fixes.py-130- continue -asmpython/_compiler/type_parameter_compat_fixes.py-131- value = node.value -asmpython/_compiler/type_parameter_compat_fixes.py-132- uses_parameter = _expression_uses_type_parameter(value, parameter) -asmpython/_compiler/type_parameter_compat_fixes.py-133- uses_related = any( -asmpython/_compiler/type_parameter_compat_fixes.py-134- isinstance(expr, A.Name) and expr.name in related -asmpython/_compiler/type_parameter_compat_fixes.py-135- for expr in _walk_expr(value) -asmpython/_compiler/type_parameter_compat_fixes.py-136- ) -asmpython/_compiler/type_parameter_compat_fixes.py-137- passes_parameter = any( -asmpython/_compiler/type_parameter_compat_fixes.py-138- isinstance(expr, A.Name) and expr.name == parameter -asmpython/_compiler/type_parameter_compat_fixes.py-139- for expr in _walk_expr(value) -asmpython/_compiler/type_parameter_compat_fixes.py:140: ) and isinstance(value, (A.Call, A.MethodCall)) -asmpython/_compiler/type_parameter_compat_fixes.py-141- if ( -asmpython/_compiler/type_parameter_compat_fixes.py-142- uses_parameter or uses_related or passes_parameter -asmpython/_compiler/type_parameter_compat_fixes.py-143- ) and node.target not in related: -asmpython/_compiler/type_parameter_compat_fixes.py-144- related.add(node.target) -asmpython/_compiler/type_parameter_compat_fixes.py-145- changed = True -asmpython/_compiler/type_parameter_compat_fixes.py-146- return related -asmpython/_compiler/type_parameter_compat_fixes.py-147- -asmpython/_compiler/type_parameter_compat_fixes.py-148- -asmpython/_compiler/type_parameter_compat_fixes.py-149-def _guarded_type_variable(test, parameter: str) -> "str | None": -asmpython/_compiler/type_parameter_compat_fixes.py-150- for expr in _walk_expr(test): -asmpython/_compiler/type_parameter_compat_fixes.py-151- if ( -asmpython/_compiler/type_parameter_compat_fixes.py-152- isinstance(expr, A.Call) -asmpython/_compiler/type_parameter_compat_fixes.py-153- and expr.func == "isinstance" -asmpython/_compiler/type_parameter_compat_fixes.py-154- and len(expr.args) == 2 -asmpython/_compiler/type_parameter_compat_fixes.py-155- and isinstance(expr.args[0], A.Name) -asmpython/_compiler/type_parameter_compat_fixes.py-156- and isinstance(expr.args[1], A.Name) -asmpython/_compiler/type_parameter_compat_fixes.py-157- and expr.args[1].name == parameter -asmpython/_compiler/type_parameter_compat_fixes.py-158- ): -asmpython/_compiler/type_parameter_compat_fixes.py-159- return expr.args[0].name -asmpython/_compiler/type_parameter_compat_fixes.py-160- return None -asmpython/_compiler/type_parameter_compat_fixes.py-161- -asmpython/_compiler/type_parameter_compat_fixes.py-162- -asmpython/_compiler/type_parameter_compat_fixes.py-163-def _returns_specialized_type(func, parameter: str) -> bool: -asmpython/_compiler/type_parameter_compat_fixes.py-164- related = _assignment_related_variables(func, parameter) -asmpython/_compiler/type_parameter_compat_fixes.py-165- saw_value = False -asmpython/_compiler/type_parameter_compat_fixes.py-166- valid = True -asmpython/_compiler/type_parameter_compat_fixes.py-167- -asmpython/_compiler/type_parameter_compat_fixes.py-168- def visit(stmts: list, guarded: set) -> None: -asmpython/_compiler/type_parameter_compat_fixes.py-169- nonlocal saw_value, valid -asmpython/_compiler/type_parameter_compat_fixes.py-170- for stmt in stmts: -asmpython/_compiler/type_parameter_compat_fixes.py-171- if isinstance(stmt, A.Return): -asmpython/_compiler/type_parameter_compat_fixes.py-172- value = stmt.value -asmpython/_compiler/type_parameter_compat_fixes.py-173- if value is None or ( -asmpython/_compiler/type_parameter_compat_fixes.py-174- isinstance(value, A.IntLit) and getattr(value, "is_none", False) -asmpython/_compiler/type_parameter_compat_fixes.py-175- ): -asmpython/_compiler/type_parameter_compat_fixes.py-176- continue -asmpython/_compiler/type_parameter_compat_fixes.py-177- saw_value = True -asmpython/_compiler/type_parameter_compat_fixes.py-178- if isinstance(value, A.Call) and value.func == parameter: -asmpython/_compiler/type_parameter_compat_fixes.py-179- continue -asmpython/_compiler/type_parameter_compat_fixes.py-180- if isinstance(value, A.Name) and ( -asmpython/_compiler/type_parameter_compat_fixes.py-181- value.name in related or value.name in guarded -asmpython/_compiler/type_parameter_compat_fixes.py-182- ): -asmpython/_compiler/type_parameter_compat_fixes.py-183- continue -asmpython/_compiler/type_parameter_compat_fixes.py-184- valid = False -asmpython/_compiler/type_parameter_compat_fixes.py-185- continue -asmpython/_compiler/type_parameter_compat_fixes.py-186- if isinstance(stmt, A.If): -asmpython/_compiler/type_parameter_compat_fixes.py-187- narrowed = _guarded_type_variable(stmt.test, parameter) -asmpython/_compiler/type_parameter_compat_fixes.py-188- then_guarded = set(guarded) -asmpython/_compiler/type_parameter_compat_fixes.py-189- if narrowed is not None: -asmpython/_compiler/type_parameter_compat_fixes.py-190- then_guarded.add(narrowed) -asmpython/_compiler/type_parameter_compat_fixes.py-191- visit(stmt.then, then_guarded) -asmpython/_compiler/type_parameter_compat_fixes.py-192- visit(stmt.orelse, guarded) -asmpython/_compiler/type_parameter_compat_fixes.py-193- continue -asmpython/_compiler/type_parameter_compat_fixes.py-194- for attr in ("body", "handler", "else_body", "finally_body"): -asmpython/_compiler/type_parameter_compat_fixes.py-195- nested = getattr(stmt, attr, None) -asmpython/_compiler/type_parameter_compat_fixes.py-196- if isinstance(nested, list): -asmpython/_compiler/type_parameter_compat_fixes.py-197- visit(nested, guarded) -asmpython/_compiler/type_parameter_compat_fixes.py-198- -asmpython/_compiler/type_parameter_compat_fixes.py-199- visit(func.body, set()) -asmpython/_compiler/type_parameter_compat_fixes.py-200- return saw_value and valid -asmpython/_compiler/type_parameter_compat_fixes.py-201- -asmpython/_compiler/type_parameter_compat_fixes.py-202- -asmpython/_compiler/type_parameter_compat_fixes.py-203-def _all_statement_lists(mod: A.Module) -> list: -asmpython/_compiler/type_parameter_compat_fixes.py-204- result = [mod.body] -asmpython/_compiler/type_parameter_compat_fixes.py-205- for func in mod.funcs: -asmpython/_compiler/type_parameter_compat_fixes.py-206- result.append(func.body) -asmpython/_compiler/type_parameter_compat_fixes.py-207- for cls in mod.classes: -asmpython/_compiler/type_parameter_compat_fixes.py-208- for method in cls.methods: -asmpython/_compiler/type_parameter_compat_fixes.py-209- result.append(method.body) -asmpython/_compiler/type_parameter_compat_fixes.py-210- return result -asmpython/_compiler/type_parameter_compat_fixes.py-211- -asmpython/_compiler/type_parameter_compat_fixes.py-212- -asmpython/_compiler/type_parameter_compat_fixes.py-213-def _all_call_nodes(mod: A.Module) -> list: -asmpython/_compiler/type_parameter_compat_fixes.py-214- calls: list = [] -asmpython/_compiler/type_parameter_compat_fixes.py-215- for stmts in _all_statement_lists(mod): -asmpython/_compiler/type_parameter_compat_fixes.py-216- for node in _walk_stmts(stmts): -asmpython/_compiler/type_parameter_compat_fixes.py:217: if isinstance(node, (A.Call, A.MethodCall)): -asmpython/_compiler/type_parameter_compat_fixes.py-218- calls.append(node) -asmpython/_compiler/type_parameter_compat_fixes.py-219- return calls -asmpython/_compiler/type_parameter_compat_fixes.py-220- -asmpython/_compiler/type_parameter_compat_fixes.py-221- -asmpython/_compiler/type_parameter_compat_fixes.py-222-def _call_sites_with_owners(mod: A.Module): -asmpython/_compiler/type_parameter_compat_fixes.py-223- """Yield ``(owning FuncDef id or None, call)`` for the complete module.""" -asmpython/_compiler/type_parameter_compat_fixes.py-224- for node in _walk_stmts(mod.body): -asmpython/_compiler/type_parameter_compat_fixes.py:225: if isinstance(node, (A.Call, A.MethodCall)): -asmpython/_compiler/type_parameter_compat_fixes.py-226- yield (None, node) -asmpython/_compiler/type_parameter_compat_fixes.py-227- for func in mod.funcs: -asmpython/_compiler/type_parameter_compat_fixes.py-228- owner_id = id(func) -asmpython/_compiler/type_parameter_compat_fixes.py-229- for node in _walk_stmts(func.body): -asmpython/_compiler/type_parameter_compat_fixes.py:230: if isinstance(node, (A.Call, A.MethodCall)): -asmpython/_compiler/type_parameter_compat_fixes.py-231- yield (owner_id, node) -asmpython/_compiler/type_parameter_compat_fixes.py-232- for cls in mod.classes: -asmpython/_compiler/type_parameter_compat_fixes.py-233- for method in cls.methods: -asmpython/_compiler/type_parameter_compat_fixes.py-234- owner_id = id(method) -asmpython/_compiler/type_parameter_compat_fixes.py-235- for node in _walk_stmts(method.body): -asmpython/_compiler/type_parameter_compat_fixes.py:236: if isinstance(node, (A.Call, A.MethodCall)): -asmpython/_compiler/type_parameter_compat_fixes.py-237- yield (owner_id, node) -asmpython/_compiler/type_parameter_compat_fixes.py-238- -asmpython/_compiler/type_parameter_compat_fixes.py-239- -asmpython/_compiler/type_parameter_compat_fixes.py-240-def _argument_binding(call, func, parameter_index: int, is_method: bool): -asmpython/_compiler/type_parameter_compat_fixes.py-241- parameter = func.params[parameter_index] -asmpython/_compiler/type_parameter_compat_fixes.py-242- offset = 0 -asmpython/_compiler/type_parameter_compat_fixes.py-243- if is_method and "staticmethod" not in getattr(func, "decorators", []): -asmpython/_compiler/type_parameter_compat_fixes.py-244- offset = 1 -asmpython/_compiler/type_parameter_compat_fixes.py-245- positional_index = parameter_index - offset -asmpython/_compiler/type_parameter_compat_fixes.py-246- if 0 <= positional_index < len(call.args): -asmpython/_compiler/type_parameter_compat_fixes.py-247- return call.args[positional_index], ("positional", positional_index) -asmpython/_compiler/type_parameter_compat_fixes.py-248- for index, (name, value) in enumerate(call.kwargs): -asmpython/_compiler/type_parameter_compat_fixes.py-249- if name == parameter: -asmpython/_compiler/type_parameter_compat_fixes.py-250- return value, ("keyword", index) -asmpython/_compiler/type_parameter_compat_fixes.py-251- return None, None -asmpython/_compiler/type_parameter_compat_fixes.py-252- -asmpython/_compiler/type_parameter_compat_fixes.py-253- -asmpython/_compiler/type_parameter_compat_fixes.py-254-def _remove_bound_argument(call, binding) -> None: -asmpython/_compiler/type_parameter_compat_fixes.py-255- kind, index = binding -asmpython/_compiler/type_parameter_compat_fixes.py-256- if kind == "positional": -asmpython/_compiler/type_parameter_compat_fixes.py-257- del call.args[index] -asmpython/_compiler/type_parameter_compat_fixes.py-258- else: -asmpython/_compiler/type_parameter_compat_fixes.py-259- del call.kwargs[index] -asmpython/_compiler/type_parameter_compat_fixes.py-260- -asmpython/_compiler/type_parameter_compat_fixes.py-261- -asmpython/_compiler/type_parameter_compat_fixes.py-262-def _sanitize(name: str) -> str: -asmpython/_compiler/type_parameter_compat_fixes.py-263- out = [] -asmpython/_compiler/type_parameter_compat_fixes.py-264- for char in name: -asmpython/_compiler/type_parameter_compat_fixes.py-265- out.append(char if char.isalnum() or char == "_" else "_") -asmpython/_compiler/type_parameter_compat_fixes.py-266- return "".join(out) -asmpython/_compiler/type_parameter_compat_fixes.py-267- -asmpython/_compiler/type_parameter_compat_fixes.py-268- -asmpython/_compiler/type_parameter_compat_fixes.py-269-def _specialized_clone(func, parameter_index: int, class_name: str, clone_name: str): -asmpython/_compiler/type_parameter_compat_fixes.py-270- cloned = _clone(func) -asmpython/_compiler/type_parameter_compat_fixes.py-271- parameter = cloned.params[parameter_index] -asmpython/_compiler/type_parameter_compat_fixes.py-272- cloned.name = clone_name -asmpython/_compiler/type_parameter_compat_fixes.py-273- del cloned.params[parameter_index] -asmpython/_compiler/type_parameter_compat_fixes.py-274- if parameter_index < len(cloned.defaults): -asmpython/_compiler/type_parameter_compat_fixes.py-275- del cloned.defaults[parameter_index] -asmpython/_compiler/type_parameter_compat_fixes.py-276- if parameter_index < len(cloned.param_types): -asmpython/_compiler/type_parameter_compat_fixes.py-277- del cloned.param_types[parameter_index] -asmpython/_compiler/type_parameter_compat_fixes.py-278- cloned.readonly_params = [ -asmpython/_compiler/type_parameter_compat_fixes.py-279- name for name in cloned.readonly_params if name != parameter -asmpython/_compiler/type_parameter_compat_fixes.py-280- ] -asmpython/_compiler/type_parameter_compat_fixes.py-281- if hasattr(func, "free_vars"): -asmpython/_compiler/type_parameter_compat_fixes.py-282- cloned.free_vars = [ -asmpython/_compiler/type_parameter_compat_fixes.py-283- name for name in getattr(func, "free_vars", []) if name != parameter -asmpython/_compiler/type_parameter_compat_fixes.py-284- ] -asmpython/_compiler/type_parameter_compat_fixes.py-285- if hasattr(func, "nonlocal_vars"): -asmpython/_compiler/type_parameter_compat_fixes.py-286- cloned.nonlocal_vars = [ -asmpython/_compiler/type_parameter_compat_fixes.py-287- name for name in getattr(func, "nonlocal_vars", []) if name != parameter -asmpython/_compiler/type_parameter_compat_fixes.py-288- ] -asmpython/_compiler/type_parameter_compat_fixes.py-289- _replace_parameter(cloned.body, parameter, class_name) -asmpython/_compiler/type_parameter_compat_fixes.py-290- if _returns_specialized_type(func, parameter): -asmpython/_compiler/type_parameter_compat_fixes.py-291- cloned.ret_type = (class_name, None) -asmpython/_compiler/type_parameter_compat_fixes.py-292- return cloned -asmpython/_compiler/type_parameter_compat_fixes.py-293- -asmpython/_compiler/type_parameter_compat_fixes.py-294- -asmpython/_compiler/type_parameter_compat_fixes.py-295-def _call_matches(call, is_method: bool, name: str) -> bool: -asmpython/_compiler/type_parameter_compat_fixes.py-296- if is_method: -asmpython/_compiler/type_parameter_compat_fixes.py:297: return isinstance(call, A.MethodCall) and call.method == name -asmpython/_compiler/type_parameter_compat_fixes.py-298- return isinstance(call, A.Call) and call.func == name -asmpython/_compiler/type_parameter_compat_fixes.py-299- -asmpython/_compiler/type_parameter_compat_fixes.py-300- -asmpython/_compiler/type_parameter_compat_fixes.py-301-def _safe_originals_to_neutralize(mod: A.Module, originals: dict) -> set: -asmpython/_compiler/type_parameter_compat_fixes.py-302- """Compute the greatest set whose only incoming calls come from that set. -asmpython/_compiler/type_parameter_compat_fixes.py-303- -asmpython/_compiler/type_parameter_compat_fixes.py-304- Calls between generic originals disappear together. Calls from module code, -asmpython/_compiler/type_parameter_compat_fixes.py-305- generated clones, or a generic original that must remain live keep their -asmpython/_compiler/type_parameter_compat_fixes.py-306- target live as well. Iterating removals computes the greatest safe set. -asmpython/_compiler/type_parameter_compat_fixes.py-307- """ -asmpython/_compiler/type_parameter_compat_fixes.py-308- candidates = set(originals) -asmpython/_compiler/type_parameter_compat_fixes.py-309- changed = True -asmpython/_compiler/type_parameter_compat_fixes.py-310- while changed: -asmpython/_compiler/type_parameter_compat_fixes.py-311- changed = False -asmpython/_compiler/type_parameter_compat_fixes.py-312- for original_id in list(candidates): -asmpython/_compiler/type_parameter_compat_fixes.py-313- _func, is_method, _owner_name, name = originals[original_id] -asmpython/_compiler/type_parameter_compat_fixes.py-314- externally_referenced = False -asmpython/_compiler/type_parameter_compat_fixes.py-315- for caller_id, call in _call_sites_with_owners(mod): -asmpython/_compiler/type_parameter_compat_fixes.py-316- if not _call_matches(call, is_method, name): -asmpython/_compiler/type_parameter_compat_fixes.py-317- continue -asmpython/_compiler/type_parameter_compat_fixes.py-318- if caller_id in candidates: -asmpython/_compiler/type_parameter_compat_fixes.py-319- continue -asmpython/_compiler/type_parameter_compat_fixes.py-320- externally_referenced = True -asmpython/_compiler/type_parameter_compat_fixes.py-321- break -asmpython/_compiler/type_parameter_compat_fixes.py-322- if externally_referenced: -asmpython/_compiler/type_parameter_compat_fixes.py-323- candidates.remove(original_id) -asmpython/_compiler/type_parameter_compat_fixes.py-324- changed = True -asmpython/_compiler/type_parameter_compat_fixes.py-325- return candidates -asmpython/_compiler/type_parameter_compat_fixes.py-326- -asmpython/_compiler/type_parameter_compat_fixes.py-327- -asmpython/_compiler/type_parameter_compat_fixes.py-328-def _lower_type_parameter_specializations(mod: A.Module) -> None: -asmpython/_compiler/type_parameter_compat_fixes.py-329- if getattr(mod, "_type_parameter_specializations_lowered", False): -asmpython/_compiler/type_parameter_compat_fixes.py-330- return -asmpython/_compiler/type_parameter_compat_fixes.py-331- mod._type_parameter_specializations_lowered = True -asmpython/_compiler/type_parameter_compat_fixes.py-332- -asmpython/_compiler/type_parameter_compat_fixes.py-333- class_names = {cls.name for cls in mod.classes} -asmpython/_compiler/type_parameter_compat_fixes.py-334- function_defs: dict = {} -asmpython/_compiler/type_parameter_compat_fixes.py-335- for func in mod.funcs: -asmpython/_compiler/type_parameter_compat_fixes.py-336- function_defs.setdefault(func.name, []).append(func) -asmpython/_compiler/type_parameter_compat_fixes.py-337- method_defs: dict = {} -asmpython/_compiler/type_parameter_compat_fixes.py-338- method_owners: dict = {} -asmpython/_compiler/type_parameter_compat_fixes.py-339- for cls in mod.classes: -asmpython/_compiler/type_parameter_compat_fixes.py-340- for method in cls.methods: -asmpython/_compiler/type_parameter_compat_fixes.py-341- method_defs.setdefault(method.name, []).append(method) -asmpython/_compiler/type_parameter_compat_fixes.py-342- method_owners.setdefault(method.name, []).append(cls) -asmpython/_compiler/type_parameter_compat_fixes.py-343- -asmpython/_compiler/type_parameter_compat_fixes.py-344- function_targets = { -asmpython/_compiler/type_parameter_compat_fixes.py-345- name: defs[0] -asmpython/_compiler/type_parameter_compat_fixes.py-346- for name, defs in function_defs.items() -asmpython/_compiler/type_parameter_compat_fixes.py-347- if len(defs) == 1 and _type_parameter_indices(defs[0], False) -asmpython/_compiler/type_parameter_compat_fixes.py-348- } -asmpython/_compiler/type_parameter_compat_fixes.py-349- method_targets = { -asmpython/_compiler/type_parameter_compat_fixes.py-350- name: defs[0] -asmpython/_compiler/type_parameter_compat_fixes.py-351- for name, defs in method_defs.items() -asmpython/_compiler/type_parameter_compat_fixes.py-352- if len(defs) == 1 and _type_parameter_indices(defs[0], True) -asmpython/_compiler/type_parameter_compat_fixes.py-353- } -asmpython/_compiler/type_parameter_compat_fixes.py-354- if not function_targets and not method_targets: -asmpython/_compiler/type_parameter_compat_fixes.py-355- return -asmpython/_compiler/type_parameter_compat_fixes.py-356- -asmpython/_compiler/type_parameter_compat_fixes.py-357- specializations: dict = {} -asmpython/_compiler/type_parameter_compat_fixes.py-358- changed = True -asmpython/_compiler/type_parameter_compat_fixes.py-359- while changed: -asmpython/_compiler/type_parameter_compat_fixes.py-360- changed = False -asmpython/_compiler/type_parameter_compat_fixes.py-361- for call in list(_all_call_nodes(mod)): -asmpython/_compiler/type_parameter_compat_fixes.py:362: is_method = isinstance(call, A.MethodCall) -asmpython/_compiler/type_parameter_compat_fixes.py-363- name = call.method if is_method else call.func -asmpython/_compiler/type_parameter_compat_fixes.py-364- func = method_targets.get(name) if is_method else function_targets.get(name) -asmpython/_compiler/type_parameter_compat_fixes.py-365- if func is None: -asmpython/_compiler/type_parameter_compat_fixes.py-366- continue -asmpython/_compiler/type_parameter_compat_fixes.py-367- indices = _type_parameter_indices(func, is_method) -asmpython/_compiler/type_parameter_compat_fixes.py-368- if not indices: -asmpython/_compiler/type_parameter_compat_fixes.py-369- continue -asmpython/_compiler/type_parameter_compat_fixes.py-370- parameter_index = indices[0] -asmpython/_compiler/type_parameter_compat_fixes.py-371- argument, binding = _argument_binding( -asmpython/_compiler/type_parameter_compat_fixes.py-372- call, -asmpython/_compiler/type_parameter_compat_fixes.py-373- func, -asmpython/_compiler/type_parameter_compat_fixes.py-374- parameter_index, -asmpython/_compiler/type_parameter_compat_fixes.py-375- is_method, -asmpython/_compiler/type_parameter_compat_fixes.py-376- ) -asmpython/_compiler/type_parameter_compat_fixes.py-377- if ( -asmpython/_compiler/type_parameter_compat_fixes.py-378- binding is None -asmpython/_compiler/type_parameter_compat_fixes.py-379- or not isinstance(argument, A.Name) -asmpython/_compiler/type_parameter_compat_fixes.py-380- or argument.name not in class_names -asmpython/_compiler/type_parameter_compat_fixes.py-381- ): -asmpython/_compiler/type_parameter_compat_fixes.py-382- continue -asmpython/_compiler/type_parameter_compat_fixes.py-383- -asmpython/_compiler/type_parameter_compat_fixes.py-384- class_name = argument.name -asmpython/_compiler/type_parameter_compat_fixes.py-385- owner_name = "" -asmpython/_compiler/type_parameter_compat_fixes.py-386- owner = None -asmpython/_compiler/type_parameter_compat_fixes.py-387- if is_method: +asmpython/_compiler/codegen.py-4010- self._gen_const_load(self.ffi_consts[expr.name]) +asmpython/_compiler/codegen.py-4011- return +asmpython/_compiler/codegen.py-4012- # Module dunders the runtime provides as string constants. A +asmpython/_compiler/codegen.py-4013- # compiled program is its own entry point, so __name__ is +asmpython/_compiler/codegen.py-4014- # "__main__"; __file__ is the entry source file's resolved path +asmpython/_compiler/codegen.py-4015- # (threaded in from the driver), or "" if compiling from a +asmpython/_compiler/codegen.py-4016- # string with no real file. +asmpython/_compiler/codegen.py-4017- if expr.name == "__name__": +asmpython/_compiler/codegen.py-4018- label, _ = self.intern_string("__main__") +asmpython/_compiler/codegen.py-4019- self.emitf(f"lea rax, [{label}]") +asmpython/_compiler/codegen.py-4020- return +asmpython/_compiler/codegen.py-4021- if expr.name == "__file__": +asmpython/_compiler/codegen.py-4022- label, _ = self.intern_string(self.entry_path or "") +asmpython/_compiler/codegen.py-4023- self.emitf(f"lea rax, [{label}]") +asmpython/_compiler/codegen.py-4024- return +asmpython/_compiler/codegen.py-4025- # A bare class name used as a value (`Stmt = Assign | AugAssign`, +asmpython/_compiler/codegen.py-4026- # `isinstance(x, Token)`, storing a class object). asmpython has no +asmpython/_compiler/codegen.py-4027- # first-class type objects; we load the class's RTTI id so the value +asmpython/_compiler/codegen.py-4028- # is stable and unique per class. (These appear in type-alias +asmpython/_compiler/codegen.py-4029- # expressions the compiled program never actually inspects.) +asmpython/_compiler/codegen.py:4030: if expr.name in self.class_ids: +asmpython/_compiler/codegen.py:4031: self.emitf(f"mov rax, {self.class_ids[expr.name]}") +asmpython/_compiler/codegen.py-4032- return +asmpython/_compiler/codegen.py:4033: if expr.name in BUILTIN_TYPE_IDS: +asmpython/_compiler/codegen.py-4034- # A bare builtin type name as a value (`{"type": str}`, +asmpython/_compiler/codegen.py-4035- # mimicking argparse's `type=str`) -- same RTTI-id trick, +asmpython/_compiler/codegen.py:4036: # see BUILTIN_TYPE_IDS. +asmpython/_compiler/codegen.py:4037: self.emitf(f"mov rax, {BUILTIN_TYPE_IDS[expr.name]}") +asmpython/_compiler/codegen.py-4038- return +asmpython/_compiler/codegen.py-4039- if expr.name in BUILTIN_EXCEPTIONS: +asmpython/_compiler/codegen.py-4040- # A bare builtin-exception name as a value (`raise X` without +asmpython/_compiler/codegen.py-4041- # parens, storing the class): exceptions are message strings, +asmpython/_compiler/codegen.py-4042- # so the class-as-value is its interned name. +asmpython/_compiler/codegen.py-4043- lbl, _ = self.intern_string(expr.name) +asmpython/_compiler/codegen.py-4044- self.emitf(f"lea rax, [{lbl}]") +asmpython/_compiler/codegen.py-4045- return +asmpython/_compiler/codegen.py-4046- if expr.name not in info.locals_ and expr.name not in self.global_vars: +asmpython/_compiler/codegen.py-4047- # A bare reference to a top-level function (not a call): used +asmpython/_compiler/codegen.py-4048- # as a callback value, e.g. `atexit.register(my_handler, ...)` +asmpython/_compiler/codegen.py-4049- # or `signal.signal(SIGINT, handler)`. Evaluate to the +asmpython/_compiler/codegen.py-4050- # function's address so it can be stored in an int slot and +asmpython/_compiler/codegen.py-4051- # called indirectly later. +asmpython/_compiler/codegen.py-4052- if any(f.name == expr.name for f in self.mod.funcs): +asmpython/_compiler/codegen.py-4053- self.emitf(f"lea rax, [rel {self._user_symbol(expr.name)}]") +asmpython/_compiler/codegen.py-4054- return +asmpython/_compiler/codegen.py-4055- # A module name (from `import X`) isn't a real heap variable — +asmpython/_compiler/codegen.py-4056- # represent it as null (0). Attribute access on it falls through +asmpython/_compiler/codegen.py-4057- # to the lenient module/any path in _gen_attr. +asmpython/_compiler/codegen.py-4058- if ( +asmpython/_compiler/codegen.py-4059- expr.inferred_type in ("module", "any") +asmpython/_compiler/codegen.py-4060- or expr.name in self.mod.imported_modules +asmpython/_compiler/codegen.py-4061- ): +asmpython/_compiler/codegen.py-4062- self.emitf("xor rax, rax") +asmpython/_compiler/codegen.py-4063- return +asmpython/_compiler/codegen.py-4064- raise NameError(f"undefined variable {expr.name}") +asmpython/_compiler/codegen.py-4065- mem = self._var_mem(expr.name, info) +asmpython/_compiler/codegen.py-4066- ty = self._var_type(expr.name, info) +asmpython/_compiler/codegen.py-4067- if expr.name in info.nonlocal_boxes: +asmpython/_compiler/codegen.py-4068- # Nonlocal: param slot holds a box ptr; deref to get the value. +asmpython/_compiler/codegen.py-4069- self.emitf(f"mov rax, {mem}", "mov rax, [rax]") +asmpython/_compiler/codegen.py-4070- elif ty == "float": +asmpython/_compiler/codegen.py-4071- self.emitf(f"movsd xmm0, {mem}") +asmpython/_compiler/codegen.py-4072- else: +asmpython/_compiler/codegen.py-4073- self.emitf(f"mov rax, {mem}") From 12acd92e4ee927207c1ae7e308d86aab0fc76b17 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:52:04 -0500 Subject: [PATCH 05/53] Run provider runtime semantic probe --- tests/cases/468_provider_type_runtime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 9ca7eb3df..f3f99fb48 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -4,6 +4,7 @@ # SomniaProvider # 1 # Covers class-valued providers and unannotated dynamic parameters. +# Semantic/backend probe generation 1. class Provider: From c8c40cd0828586e13f6e849862aba4dabbb9ab8f Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:53:25 -0500 Subject: [PATCH 06/53] Run provider runtime semantic probe on beta workflow --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index f3f99fb48..9b9aff309 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -4,7 +4,7 @@ # SomniaProvider # 1 # Covers class-valued providers and unannotated dynamic parameters. -# Semantic/backend probe generation 1. +# Semantic/backend probe generation 2. class Provider: From c4baa610cc4df195a1af0f614378563a4a203003 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:57:22 -0500 Subject: [PATCH 07/53] Trigger provider runtime PR verifier --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 9b9aff309..c7a64c556 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -4,7 +4,7 @@ # SomniaProvider # 1 # Covers class-valued providers and unannotated dynamic parameters. -# Semantic/backend probe generation 2. +# Semantic/backend probe generation 3. class Provider: From f23bf6c8c9257a8c288167116f164e5f70527718 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:59:50 -0500 Subject: [PATCH 08/53] Run structural provider class probe --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index c7a64c556..b61f9f67e 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -4,7 +4,7 @@ # SomniaProvider # 1 # Covers class-valued providers and unannotated dynamic parameters. -# Semantic/backend probe generation 3. +# Semantic/backend probe generation 4. class Provider: From 9422d28148555a6e0329744257447721d4c69291 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:02:04 -0500 Subject: [PATCH 09/53] Trigger indexed provider runtime verifier --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index b61f9f67e..8f4093d8d 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -4,7 +4,7 @@ # SomniaProvider # 1 # Covers class-valued providers and unannotated dynamic parameters. -# Semantic/backend probe generation 4. +# Semantic/backend probe generation 5. class Provider: From 0d36fab1c51dd02742830eb16d72edfdd00a38c9 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:27:47 -0500 Subject: [PATCH 10/53] Add finite class-value lowering pass --- .../_compiler/class_value_compat_fixes.py | 216 ++++++++++++++++++ 1 file changed, 216 insertions(+) create mode 100644 asmpython/_compiler/class_value_compat_fixes.py diff --git a/asmpython/_compiler/class_value_compat_fixes.py b/asmpython/_compiler/class_value_compat_fixes.py new file mode 100644 index 000000000..eb0a4c9c1 --- /dev/null +++ b/asmpython/_compiler/class_value_compat_fixes.py @@ -0,0 +1,216 @@ +"""Lower finite class-valued collections before semantic analysis. + +Native whole-program compilation can statically resolve class objects stored +in literal tuples. This pass folds literal tuple indexing and unrolls safe +loops over those tuples, replacing the loop variable with each concrete +class. It preserves ordinary Python source while avoiding a general dynamic +metatype runtime for cases whose complete class set is already known. +""" + +from __future__ import annotations + +from dataclasses import fields, is_dataclass + +from . import ast_nodes as A +from .sema import SemaAnalyzer + + +_ORIGINAL_ANALYZE = SemaAnalyzer.analyze + + +class _Splice: + def __init__(self, items: list) -> None: + self.items = items + + +def _clone(value): + if isinstance(value, list): + return [_clone(item) for item in value] + if isinstance(value, tuple): + return tuple(_clone(item) for item in value) + if isinstance(value, dict): + return {_clone(key): _clone(item) for key, item in value.items()} + if isinstance(value, set): + return {_clone(item) for item in value} + if is_dataclass(value) and not isinstance(value, type): + cls = type(value) + init_values = {} + deferred = [] + for data_field in fields(value): + cloned_value = _clone(getattr(value, data_field.name)) + if data_field.init: + init_values[data_field.name] = cloned_value + else: + deferred.append((data_field.name, cloned_value)) + cloned = cls(**init_values) + for name, cloned_value in deferred: + setattr(cloned, name, cloned_value) + return cloned + return value + + +def _static_class_tuples(mod: A.Module) -> dict: + class_names = {definition.name for definition in mod.classes} + result = {} + for statement in mod.body: + if not isinstance(statement, A.Assign): + continue + if not isinstance(statement.value, A.TupleLit): + continue + names = [] + valid = True + for element in statement.value.elems: + if not isinstance(element, A.Name) or element.name not in class_names: + valid = False + break + names.append(element.name) + if valid and names: + result[statement.target] = tuple(names) + return result + + +def _contains_loop_control(value) -> bool: + if isinstance(value, (A.Break, A.Continue)): + return True + if isinstance(value, (str, int, float, bool, type(None))): + return False + if isinstance(value, (list, tuple, set)): + return any(_contains_loop_control(item) for item in value) + if isinstance(value, dict): + return any( + _contains_loop_control(key) or _contains_loop_control(item) + for key, item in value.items() + ) + if is_dataclass(value) and not isinstance(value, type): + return any( + _contains_loop_control(getattr(value, data_field.name)) + for data_field in fields(value) + ) + return False + + +def _binds_name(value, name: str) -> bool: + if isinstance(value, A.Assign) and value.target == name: + return True + if isinstance(value, A.MultiAssign) and name in value.targets: + return True + if isinstance(value, A.For): + if value.var == name or name in value.targets: + return True + if isinstance(value, (str, int, float, bool, type(None))): + return False + if isinstance(value, (list, tuple, set)): + return any(_binds_name(item, name) for item in value) + if isinstance(value, dict): + return any( + _binds_name(key, name) or _binds_name(item, name) + for key, item in value.items() + ) + if is_dataclass(value) and not isinstance(value, type): + return any( + _binds_name(getattr(value, data_field.name), name) + for data_field in fields(value) + ) + return False + + +def _rewrite(value, class_tuples: dict, substitutions: dict): + if value is None or isinstance(value, (str, int, float, bool)): + return value + if isinstance(value, list): + rewritten = [] + for item in value: + transformed = _rewrite(item, class_tuples, substitutions) + if isinstance(transformed, _Splice): + rewritten.extend(transformed.items) + else: + rewritten.append(transformed) + return rewritten + if isinstance(value, tuple): + return tuple(_rewrite(item, class_tuples, substitutions) for item in value) + if isinstance(value, dict): + return { + _rewrite(key, class_tuples, substitutions): _rewrite( + item, class_tuples, substitutions + ) + for key, item in value.items() + } + if isinstance(value, set): + return {_rewrite(item, class_tuples, substitutions) for item in value} + + if isinstance(value, A.Name) and value.name in substitutions: + return A.Name(name=substitutions[value.name], pos=value.pos) + + if isinstance(value, A.Call) and value.func in substitutions: + value.func = substitutions[value.func] + + if isinstance(value, A.Subscript): + value.obj = _rewrite(value.obj, class_tuples, substitutions) + value.index = _rewrite(value.index, class_tuples, substitutions) + if ( + isinstance(value.obj, A.Name) + and value.obj.name in class_tuples + and isinstance(value.index, A.IntLit) + ): + entries = class_tuples[value.obj.name] + index = value.index.value + if index < 0: + index += len(entries) + if 0 <= index < len(entries): + return A.Name(name=entries[index], pos=value.pos) + return value + + if isinstance(value, A.For): + value.iter = _rewrite(value.iter, class_tuples, substitutions) + if ( + isinstance(value.iter, A.Name) + and value.iter.name in class_tuples + and not value.targets + and not value.orelse + and not _contains_loop_control(value.body) + and not _binds_name(value.body, value.var) + ): + expanded = [] + for class_name in class_tuples[value.iter.name]: + body = _clone(value.body) + local_substitutions = dict(substitutions) + local_substitutions[value.var] = class_name + transformed = _rewrite(body, class_tuples, local_substitutions) + expanded.extend(transformed) + return _Splice(expanded) + + if is_dataclass(value) and not isinstance(value, type): + for data_field in fields(value): + current = getattr(value, data_field.name) + transformed = _rewrite(current, class_tuples, substitutions) + if isinstance(transformed, _Splice): + raise TypeError( + "class-value loop expansion requires a statement list" + ) + setattr(value, data_field.name, transformed) + return value + + +def _lower_finite_class_values(mod: A.Module) -> None: + if getattr(mod, "_finite_class_values_lowered", False): + return + mod._finite_class_values_lowered = True + class_tuples = _static_class_tuples(mod) + if not class_tuples: + return + mod.body = _rewrite(mod.body, class_tuples, {}) + for function in mod.funcs: + function.body = _rewrite(function.body, class_tuples, {}) + for owner in mod.classes: + for method in owner.methods: + method.body = _rewrite(method.body, class_tuples, {}) + + +def _analyze_with_finite_class_values(self: SemaAnalyzer) -> None: + _lower_finite_class_values(self.mod) + _ORIGINAL_ANALYZE(self) + + +if not getattr(SemaAnalyzer, "_asmpython_finite_class_value_patch", False): + SemaAnalyzer.analyze = _analyze_with_finite_class_values + SemaAnalyzer._asmpython_finite_class_value_patch = True From 6726b167c3d21caca1494d8d50803d110f3908f7 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:28:02 -0500 Subject: [PATCH 11/53] Load finite class-value lowering --- asmpython/_compiler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/asmpython/_compiler/__init__.py b/asmpython/_compiler/__init__.py index 9eb76db35..e854ab574 100644 --- a/asmpython/_compiler/__init__.py +++ b/asmpython/_compiler/__init__.py @@ -21,5 +21,6 @@ from . import ordered_flow_compat_fixes as _ordered_flow_compat_fixes from . import descriptor_precedence_compat_fixes as _descriptor_precedence_compat_fixes from . import return_annotation_precedence_compat_fixes as _return_annotation_precedence_compat_fixes +from . import class_value_compat_fixes as _class_value_compat_fixes __all__ = ["__version__"] From 567d3fad62369d16181ca9f75443dd0e3fe1f3de Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:28:14 -0500 Subject: [PATCH 12/53] Focus provider type runtime regression --- tests/cases/468_provider_type_runtime.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 8f4093d8d..617e56c66 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -1,11 +1,7 @@ # expect: # 1 # 0 -# SomniaProvider # 1 -# Covers class-valued providers and unannotated dynamic parameters. -# Semantic/backend probe generation 5. - class Provider: runtime_realms = ("server", "client") @@ -30,5 +26,4 @@ def starts_with_somnia(value) -> bool: provider_types = (ServerProvider, ClientProvider) print(provider_types[0].supports_realm("server")) print(provider_types[1].supports_realm("server")) -print(str(Provider)) print(starts_with_somnia("somnia.Scene")) From 21a10f5b1fd6bab81ce87928c75f8c2868607b12 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:30:08 -0500 Subject: [PATCH 13/53] Document finite class tuple regression --- tests/cases/468_provider_type_runtime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 617e56c66..9e642bb6d 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -2,6 +2,7 @@ # 1 # 0 # 1 +# Finite class tuples must lower without a dynamic metatype runtime. class Provider: runtime_realms = ("server", "client") From 155cba1830c74dee0b8f4519f5a042a995033121 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:35:43 -0500 Subject: [PATCH 14/53] ci: rerun provider runtime diagnostic --- tests/cases/468_provider_type_runtime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 9e642bb6d..6cc8d5bcf 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -3,6 +3,7 @@ # 0 # 1 # Finite class tuples must lower without a dynamic metatype runtime. +# Diagnostic generation 2. class Provider: runtime_realms = ("server", "client") From 4b1b5cf01a23b8ad5f4927d58a2ef52602fd5d00 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:40:56 -0500 Subject: [PATCH 15/53] Materialize inherited class methods for finite class values --- .../_compiler/class_value_compat_fixes.py | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/asmpython/_compiler/class_value_compat_fixes.py b/asmpython/_compiler/class_value_compat_fixes.py index eb0a4c9c1..3238ee6a1 100644 --- a/asmpython/_compiler/class_value_compat_fixes.py +++ b/asmpython/_compiler/class_value_compat_fixes.py @@ -3,8 +3,8 @@ Native whole-program compilation can statically resolve class objects stored in literal tuples. This pass folds literal tuple indexing and unrolls safe loops over those tuples, replacing the loop variable with each concrete -class. It preserves ordinary Python source while avoiding a general dynamic -metatype runtime for cases whose complete class set is already known. +class. It also materializes inherited class/static methods on those concrete +classes so backend symbol emission preserves Python's subclass ``cls`` value. """ from __future__ import annotations @@ -69,6 +69,44 @@ def _static_class_tuples(mod: A.Module) -> dict: return result +def _materialize_inherited_class_methods(mod: A.Module, class_tuples: dict) -> None: + """Emit subclass symbols for inherited class/static methods. + + Backends key method bodies by ``ClassName__method``. A classmethod invoked + through a concrete subclass must still receive that subclass as ``cls``. + Copying the nearest inherited class/static method onto each finite concrete + class preserves that behavior and gives codegen the symbol it already + expects, without introducing a dynamic metatype runtime. + """ + class_table = {definition.name: definition for definition in mod.classes} + concrete_names = { + class_name + for entries in class_tuples.values() + for class_name in entries + } + for class_name in concrete_names: + owner = class_table.get(class_name) + if owner is None: + continue + existing = {method.name for method in owner.methods} + parent_name = owner.parent + seen = set() + while parent_name and parent_name not in seen: + seen.add(parent_name) + parent = class_table.get(parent_name) + if parent is None: + break + for method in parent.methods: + decorators = set(getattr(method, "decorators", [])) + if method.name in existing: + continue + if not decorators.intersection({"classmethod", "staticmethod"}): + continue + owner.methods.append(_clone(method)) + existing.add(method.name) + parent_name = parent.parent + + def _contains_loop_control(value) -> bool: if isinstance(value, (A.Break, A.Continue)): return True @@ -198,6 +236,7 @@ def _lower_finite_class_values(mod: A.Module) -> None: class_tuples = _static_class_tuples(mod) if not class_tuples: return + _materialize_inherited_class_methods(mod, class_tuples) mod.body = _rewrite(mod.body, class_tuples, {}) for function in mod.funcs: function.body = _rewrite(function.body, class_tuples, {}) From 59006d9c2e2cce5fa91ca7ada95a8b3bdbc5520d Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:42:42 -0500 Subject: [PATCH 16/53] ci: rerun Somnia provider parity diagnostic --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 6cc8d5bcf..2c498cf4a 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -3,7 +3,7 @@ # 0 # 1 # Finite class tuples must lower without a dynamic metatype runtime. -# Diagnostic generation 2. +# Diagnostic generation 3. class Provider: runtime_realms = ("server", "client") From c9bbbdf67be981ac8d70827d13571cf7f58676e2 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:50:03 -0500 Subject: [PATCH 17/53] Infer dynamic value returns and stringify class objects --- .../_compiler/dynamic_value_compat_fixes.py | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 asmpython/_compiler/dynamic_value_compat_fixes.py diff --git a/asmpython/_compiler/dynamic_value_compat_fixes.py b/asmpython/_compiler/dynamic_value_compat_fixes.py new file mode 100644 index 000000000..1f551165c --- /dev/null +++ b/asmpython/_compiler/dynamic_value_compat_fixes.py @@ -0,0 +1,98 @@ +"""Compatibility for statically recoverable dynamic Python values. + +The whole-program return pass and semantic checker historically disagreed on a +few common dynamic forms. This module aligns return inference for ``dict.get``, +``getattr`` and ``.__name__`` with the later field-flow pass, and folds direct +``str(UserClass)`` calls to a stable class representation before codegen. +""" + +from __future__ import annotations + +from . import analysis_compat_fixes as analysis +from . import ast_nodes as A +from .sema import SemaAnalyzer + + +_ORIGINAL_EXPRESSION_ANNOTATION = analysis._expression_annotation +_ORIGINAL_CHECK_CALL = SemaAnalyzer._check_call + + +def _expression_annotation_with_dynamic_values( + expression, + environment: dict, + owner_name, + function_returns: dict, + method_returns: dict, + class_names: set, + parents: dict, +) -> str: + if isinstance(expression, A.Attr) and expression.name == "__name__": + return "str" + + if isinstance(expression, A.Call) and expression.func == "getattr": + if len(expression.args) >= 3: + return analysis._expression_annotation( + expression.args[2], + environment, + owner_name, + function_returns, + method_returns, + class_names, + parents, + ) + if ( + len(expression.args) >= 2 + and isinstance(expression.args[1], A.StrLit) + and expression.args[1].value == "__name__" + ): + return "str" + + if ( + isinstance(expression, A.MethodCall) + and expression.method == "get" + and len(expression.args) >= 2 + ): + default_type = analysis._expression_annotation( + expression.args[1], + environment, + owner_name, + function_returns, + method_returns, + class_names, + parents, + ) + if default_type not in (analysis._UNKNOWN, analysis._NONE): + return default_type + + return _ORIGINAL_EXPRESSION_ANNOTATION( + expression, + environment, + owner_name, + function_returns, + method_returns, + class_names, + parents, + ) + + +def _check_call_with_static_class_str(self: SemaAnalyzer, expression, scope) -> None: + if ( + isinstance(expression, A.Call) + and expression.func == "str" + and len(expression.args) == 1 + and isinstance(expression.args[0], A.Name) + and expression.args[0].name in self.classes + ): + class_name = expression.args[0].name + expression.args[0] = A.StrLit( + value="", + pos=expression.args[0].pos, + ) + _ORIGINAL_CHECK_CALL(self, expression, scope) + + +analysis._expression_annotation = _expression_annotation_with_dynamic_values + +if not getattr(SemaAnalyzer, "_asmpython_dynamic_value_patch", False): + SemaAnalyzer._check_call = _check_call_with_static_class_str + SemaAnalyzer._asmpython_dynamic_value_patch = True From 07b9303e32735026338872a35074380d5c297d0f Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:50:24 -0500 Subject: [PATCH 18/53] Load dynamic value compatibility fixes --- asmpython/_compiler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/asmpython/_compiler/__init__.py b/asmpython/_compiler/__init__.py index e854ab574..9a9dbf84f 100644 --- a/asmpython/_compiler/__init__.py +++ b/asmpython/_compiler/__init__.py @@ -22,5 +22,6 @@ from . import descriptor_precedence_compat_fixes as _descriptor_precedence_compat_fixes from . import return_annotation_precedence_compat_fixes as _return_annotation_precedence_compat_fixes from . import class_value_compat_fixes as _class_value_compat_fixes +from . import dynamic_value_compat_fixes as _dynamic_value_compat_fixes __all__ = ["__version__"] From c7799e2432bd4cf8f55fb30a8cbb28fbc892c975 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:50:50 -0500 Subject: [PATCH 19/53] Test static class string conversion --- tests/cases/468_provider_type_runtime.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 2c498cf4a..f2c6a61e2 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -2,8 +2,9 @@ # 1 # 0 # 1 +# 1 # Finite class tuples must lower without a dynamic metatype runtime. -# Diagnostic generation 3. +# Dynamic values must preserve string behavior. class Provider: runtime_realms = ("server", "client") @@ -29,3 +30,4 @@ def starts_with_somnia(value) -> bool: print(provider_types[0].supports_realm("server")) print(provider_types[1].supports_realm("server")) print(starts_with_somnia("somnia.Scene")) +print("Provider" in str(Provider)) From 8705250d248bb617df111c7ee23f1db46ef2ef7d Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:51:39 -0500 Subject: [PATCH 20/53] ci: verify exact provider regression output --- tests/cases/468_provider_type_runtime.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index f2c6a61e2..68af1d51b 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -5,6 +5,7 @@ # 1 # Finite class tuples must lower without a dynamic metatype runtime. # Dynamic values must preserve string behavior. +# Exact-output verification generation 1. class Provider: runtime_realms = ("server", "client") From 9b163900e298f10389b38b7f1483b14ffbac7e50 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:54:58 -0500 Subject: [PATCH 21/53] Specialize inherited classmethods and concrete call parameters --- .../concrete_specialization_compat_fixes.py | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 asmpython/_compiler/concrete_specialization_compat_fixes.py diff --git a/asmpython/_compiler/concrete_specialization_compat_fixes.py b/asmpython/_compiler/concrete_specialization_compat_fixes.py new file mode 100644 index 000000000..7cf08b1e4 --- /dev/null +++ b/asmpython/_compiler/concrete_specialization_compat_fixes.py @@ -0,0 +1,210 @@ +"""Whole-program specialization for concrete class and argument values. + +Finite class lowering may materialize an inherited classmethod on a concrete +subclass so native codegen has a ``Subclass__method`` symbol. Python still +requires ``cls`` inside that body to be the concrete subclass. This pass turns +those copied methods into static, subclass-specialized bodies. + +It also annotates an otherwise-unannotated top-level parameter when every call +site supplies the same statically-known value kind. This keeps ordinary dynamic +Python source while giving native method dispatch the concrete representation it +needs (for example a string parameter used with ``startswith``). +""" + +from __future__ import annotations + +from dataclasses import fields, is_dataclass + +from . import ast_nodes as A +from . import class_value_compat_fixes as class_values +from .metaclass_compat_fixes import _walk_stmts +from .sema import SemaAnalyzer + + +_ORIGINAL_ANALYZE = SemaAnalyzer.analyze + + +def _replace_identifier(value, old: str, new: str) -> None: + if value is None or isinstance(value, (str, int, float, bool)): + return + if isinstance(value, A.Name): + if value.name == old: + value.name = new + return + if isinstance(value, A.Call) and value.func == old: + value.func = new + if isinstance(value, list) or isinstance(value, tuple): + for item in value: + _replace_identifier(item, old, new) + return + if isinstance(value, dict): + for key, item in value.items(): + _replace_identifier(key, old, new) + _replace_identifier(item, old, new) + return + if is_dataclass(value) and not isinstance(value, type): + for data_field in fields(value): + _replace_identifier(getattr(value, data_field.name), old, new) + + +def _nearest_parent_method(owner, method_name: str, class_table: dict): + parent_name = owner.parent + seen = set() + while parent_name and parent_name not in seen: + seen.add(parent_name) + parent = class_table.get(parent_name) + if parent is None: + return None + for method in parent.methods: + if method.name == method_name: + return method + parent_name = parent.parent + return None + + +def _specialize_materialized_classmethods(mod: A.Module, class_tuples: dict) -> None: + class_table = {owner.name: owner for owner in mod.classes} + concrete_names = { + class_name + for entries in class_tuples.values() + for class_name in entries + } + for class_name in concrete_names: + owner = class_table.get(class_name) + if owner is None: + continue + for method in owner.methods: + decorators = list(getattr(method, "decorators", []) or []) + if "classmethod" not in decorators or not method.params: + continue + inherited = _nearest_parent_method(owner, method.name, class_table) + if inherited is None: + continue + inherited_decorators = list(getattr(inherited, "decorators", []) or []) + # The finite-class pass copies the nearest method verbatim, preserving + # source position. Do not alter an explicit subclass override. + if "classmethod" not in inherited_decorators or method.pos != inherited.pos: + continue + receiver = method.params[0] + _replace_identifier(method.body, receiver, class_name) + del method.params[0] + if method.defaults: + del method.defaults[0] + if method.param_types: + del method.param_types[0] + method.readonly_params = [ + name for name in method.readonly_params if name != receiver + ] + method.decorators = [ + decorator + for decorator in decorators + if decorator != "classmethod" + ] + if "staticmethod" not in method.decorators: + method.decorators.append("staticmethod") + + +def _literal_annotation(expression, class_names: set): + if isinstance(expression, A.StrLit): + return ("str", None) + if isinstance(expression, A.FloatLit): + return ("float", None) + if isinstance(expression, A.IntLit): + if getattr(expression, "is_none", False): + return None + return ("bool" if getattr(expression, "is_bool", False) else "int", None) + if isinstance(expression, A.ListLit): + return ("list", getattr(expression, "el_type", None)) + if isinstance(expression, A.DictLit): + return ("dict", None) + if isinstance(expression, A.TupleLit): + return ("tuple", None) + if isinstance(expression, A.SetLit): + return ("set", None) + if isinstance(expression, A.Call) and expression.func in class_names: + return (expression.func, None) + if isinstance(expression, A.Name) and expression.name in class_names: + return ("type", None) + return None + + +def _all_calls(mod: A.Module) -> list: + calls = [] + statement_lists = [mod.body] + statement_lists.extend(function.body for function in mod.funcs) + for owner in mod.classes: + statement_lists.extend(method.body for method in owner.methods) + for statements in statement_lists: + for node in _walk_stmts(statements): + if isinstance(node, A.Call): + calls.append(node) + return calls + + +def _bound_argument(call: A.Call, function, parameter_index: int): + if parameter_index < len(call.args): + return call.args[parameter_index] + parameter = function.params[parameter_index] + for name, value in call.kwargs: + if name == parameter: + return value + if parameter_index < len(function.defaults): + return function.defaults[parameter_index] + return None + + +def _specialize_unanimous_function_parameters(mod: A.Module) -> None: + class_names = {owner.name for owner in mod.classes} + definitions = {} + duplicates = set() + for function in mod.funcs: + if function.name in definitions: + duplicates.add(function.name) + else: + definitions[function.name] = function + for name in duplicates: + definitions.pop(name, None) + + calls_by_name = {} + for call in _all_calls(mod): + calls_by_name.setdefault(call.func, []).append(call) + + for name, function in definitions.items(): + calls = calls_by_name.get(name, []) + if not calls: + continue + param_types = list(function.param_types) + while len(param_types) < len(function.params): + param_types.append(None) + changed = False + for parameter_index in range(len(function.params)): + if param_types[parameter_index] is not None: + continue + observed = [] + complete = True + for call in calls: + argument = _bound_argument(call, function, parameter_index) + annotation = _literal_annotation(argument, class_names) + if annotation is None: + complete = False + break + observed.append(annotation) + if complete and observed and all(value == observed[0] for value in observed): + param_types[parameter_index] = observed[0] + changed = True + if changed: + function.param_types = param_types + + +def _analyze_with_concrete_specializations(self: SemaAnalyzer) -> None: + class_values._lower_finite_class_values(self.mod) + class_tuples = class_values._static_class_tuples(self.mod) + if class_tuples: + _specialize_materialized_classmethods(self.mod, class_tuples) + _specialize_unanimous_function_parameters(self.mod) + _ORIGINAL_ANALYZE(self) + + +if not getattr(SemaAnalyzer, "_asmpython_concrete_specialization_patch", False): + SemaAnalyzer.analyze = _analyze_with_concrete_specializations + SemaAnalyzer._asmpython_concrete_specialization_patch = True From aa5ec7b86759dca2c723393d66e14bf1020edb78 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:55:18 -0500 Subject: [PATCH 22/53] Load concrete whole-program specializations --- asmpython/_compiler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/asmpython/_compiler/__init__.py b/asmpython/_compiler/__init__.py index 9a9dbf84f..037a3a157 100644 --- a/asmpython/_compiler/__init__.py +++ b/asmpython/_compiler/__init__.py @@ -23,5 +23,6 @@ from . import return_annotation_precedence_compat_fixes as _return_annotation_precedence_compat_fixes from . import class_value_compat_fixes as _class_value_compat_fixes from . import dynamic_value_compat_fixes as _dynamic_value_compat_fixes +from . import concrete_specialization_compat_fixes as _concrete_specialization_compat_fixes __all__ = ["__version__"] From 47be24f3586e7adbe5dbdacf7f94077522cf5422 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:58:50 -0500 Subject: [PATCH 23/53] Fold concrete class variables in specialized classmethods --- .../concrete_specialization_compat_fixes.py | 63 ++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/asmpython/_compiler/concrete_specialization_compat_fixes.py b/asmpython/_compiler/concrete_specialization_compat_fixes.py index 7cf08b1e4..f6150f396 100644 --- a/asmpython/_compiler/concrete_specialization_compat_fixes.py +++ b/asmpython/_compiler/concrete_specialization_compat_fixes.py @@ -3,7 +3,8 @@ Finite class lowering may materialize an inherited classmethod on a concrete subclass so native codegen has a ``Subclass__method`` symbol. Python still requires ``cls`` inside that body to be the concrete subclass. This pass turns -those copied methods into static, subclass-specialized bodies. +those copied methods into static, subclass-specialized bodies and folds reads of +concrete class variables to their nearest class-body initializer. It also annotates an otherwise-unannotated top-level parameter when every call site supplies the same statically-known value kind. This keeps ordinary dynamic @@ -62,6 +63,63 @@ def _nearest_parent_method(owner, method_name: str, class_table: dict): return None +def _class_variable_initializer(class_name: str, field_name: str, class_table: dict): + current_name = class_name + seen = set() + while current_name and current_name not in seen: + seen.add(current_name) + owner = class_table.get(current_name) + if owner is None: + return None + for name, _annotation, initializer in owner.class_vars: + if name == field_name and initializer is not None: + return class_values._clone(initializer) + current_name = owner.parent + return None + + +def _fold_concrete_class_attributes(value, class_name: str, class_table: dict): + if value is None or isinstance(value, (str, int, float, bool)): + return value + if isinstance(value, list): + return [ + _fold_concrete_class_attributes(item, class_name, class_table) + for item in value + ] + if isinstance(value, tuple): + return tuple( + _fold_concrete_class_attributes(item, class_name, class_table) + for item in value + ) + if isinstance(value, dict): + return { + _fold_concrete_class_attributes(key, class_name, class_table): + _fold_concrete_class_attributes(item, class_name, class_table) + for key, item in value.items() + } + if isinstance(value, A.Attr): + value.obj = _fold_concrete_class_attributes( + value.obj, class_name, class_table + ) + if isinstance(value.obj, A.Name) and value.obj.name == class_name: + initializer = _class_variable_initializer( + class_name, value.name, class_table + ) + if initializer is not None: + return initializer + return value + if is_dataclass(value) and not isinstance(value, type): + for data_field in fields(value): + setattr( + value, + data_field.name, + _fold_concrete_class_attributes( + getattr(value, data_field.name), class_name, class_table + ), + ) + return value + + def _specialize_materialized_classmethods(mod: A.Module, class_tuples: dict) -> None: class_table = {owner.name: owner for owner in mod.classes} concrete_names = { @@ -87,6 +145,9 @@ def _specialize_materialized_classmethods(mod: A.Module, class_tuples: dict) -> continue receiver = method.params[0] _replace_identifier(method.body, receiver, class_name) + method.body = _fold_concrete_class_attributes( + method.body, class_name, class_table + ) del method.params[0] if method.defaults: del method.defaults[0] From ef78fcb755ee609ed6d85f874d14b272d84be035 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:01:21 -0500 Subject: [PATCH 24/53] ci: inspect specialized provider AST --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 68af1d51b..342846a6e 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -5,7 +5,7 @@ # 1 # Finite class tuples must lower without a dynamic metatype runtime. # Dynamic values must preserve string behavior. -# Exact-output verification generation 1. +# AST inspection generation 1. class Provider: runtime_realms = ("server", "client") From 5721d78966a5c40310496f45e937c729ee118ce1 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:03:05 -0500 Subject: [PATCH 25/53] Isolate tuple membership and static method typing --- tests/cases/468_provider_type_runtime.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 342846a6e..9cd335b99 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -3,9 +3,11 @@ # 0 # 1 # 1 +# 1 +# 1 # Finite class tuples must lower without a dynamic metatype runtime. # Dynamic values must preserve string behavior. -# AST inspection generation 1. +# Tuple-membership isolation generation 1. class Provider: runtime_realms = ("server", "client") @@ -23,6 +25,12 @@ class ClientProvider(Provider): runtime_realms = ("client",) +class StaticProbe: + @staticmethod + def contains_server(realm: str) -> bool: + return realm in ("server",) + + def starts_with_somnia(value) -> bool: return value.startswith("somnia.") @@ -32,3 +40,5 @@ def starts_with_somnia(value) -> bool: print(provider_types[1].supports_realm("server")) print(starts_with_somnia("somnia.Scene")) print("Provider" in str(Provider)) +print("server" in ("server",)) +print(StaticProbe.contains_server("server")) From 470b9f576bc6aa157401c8d781c4b4ccdccfe71b Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:04:16 -0500 Subject: [PATCH 26/53] ci: run isolated membership regression --- tests/cases/468_provider_type_runtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 9cd335b99..1a3b08460 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -7,7 +7,7 @@ # 1 # Finite class tuples must lower without a dynamic metatype runtime. # Dynamic values must preserve string behavior. -# Tuple-membership isolation generation 1. +# Tuple-membership isolation generation 2. class Provider: runtime_realms = ("server", "client") From 24d7f2fa144bb336875de871bec5c8b1311641ae Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:06:56 -0500 Subject: [PATCH 27/53] Lower static and class method calls to direct functions --- .../static_method_call_compat_fixes.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 asmpython/_compiler/static_method_call_compat_fixes.py diff --git a/asmpython/_compiler/static_method_call_compat_fixes.py b/asmpython/_compiler/static_method_call_compat_fixes.py new file mode 100644 index 000000000..50879004f --- /dev/null +++ b/asmpython/_compiler/static_method_call_compat_fixes.py @@ -0,0 +1,158 @@ +"""Lower class-qualified static/class method calls to direct functions. + +The native backend's historical class-qualified call path passes an implicit +class slot even for ``staticmethod`` and does not preserve Python's concrete +``cls`` semantics for inherited ``classmethod`` calls. Whole-program source +already knows the receiver class, so materialize a normal module function and +rewrite the call before semantic analysis. +""" + +from __future__ import annotations + +from dataclasses import fields, is_dataclass + +from . import ast_nodes as A +from . import class_value_compat_fixes as class_values +from . import concrete_specialization_compat_fixes as concrete +from .sema import SemaAnalyzer + + +_ORIGINAL_ANALYZE = SemaAnalyzer.analyze + + +def _sanitize(value: str) -> str: + return "".join( + character if character.isalnum() or character == "_" else "_" + for character in value + ) + + +def _resolve_class_method(class_name: str, method_name: str, class_table: dict): + current_name = class_name + seen = set() + while current_name and current_name not in seen: + seen.add(current_name) + owner = class_table.get(current_name) + if owner is None: + return None + for method in owner.methods: + if method.name == method_name: + return owner, method + current_name = owner.parent + return None + + +def _prepare_clone(method, receiver_class: str, class_table: dict): + cloned = class_values._clone(method) + decorators = list(getattr(cloned, "decorators", []) or []) + if "classmethod" in decorators: + if not cloned.params: + return None + receiver = cloned.params[0] + concrete._replace_identifier(cloned.body, receiver, receiver_class) + cloned.body = concrete._fold_concrete_class_attributes( + cloned.body, receiver_class, class_table + ) + del cloned.params[0] + if cloned.defaults: + del cloned.defaults[0] + if cloned.param_types: + del cloned.param_types[0] + cloned.readonly_params = [ + name for name in cloned.readonly_params if name != receiver + ] + cloned.decorators = [] + return cloned + + +def _lower_class_qualified_calls(mod: A.Module) -> None: + if getattr(mod, "_class_qualified_calls_lowered", False): + return + mod._class_qualified_calls_lowered = True + + class_values._lower_finite_class_values(mod) + class_tuples = class_values._static_class_tuples(mod) + if class_tuples: + concrete._specialize_materialized_classmethods(mod, class_tuples) + + class_table = {owner.name: owner for owner in mod.classes} + generated = {} + pending_functions = [] + + def ensure_function(receiver_class: str, method_name: str): + key = (receiver_class, method_name) + existing = generated.get(key) + if existing is not None: + return existing + resolved = _resolve_class_method(receiver_class, method_name, class_table) + if resolved is None: + return None + _owner, method = resolved + decorators = set(getattr(method, "decorators", []) or []) + if not decorators.intersection({"staticmethod", "classmethod"}): + return None + cloned = _prepare_clone(method, receiver_class, class_table) + if cloned is None: + return None + symbol = ( + "__asmpy_classcall_" + + _sanitize(receiver_class) + + "_" + + _sanitize(method_name) + ) + cloned.name = symbol + generated[key] = symbol + pending_functions.append(cloned) + return symbol + + def rewrite(value): + if value is None or isinstance(value, (str, int, float, bool)): + return value + if isinstance(value, list): + return [rewrite(item) for item in value] + if isinstance(value, tuple): + return tuple(rewrite(item) for item in value) + if isinstance(value, dict): + return {rewrite(key): rewrite(item) for key, item in value.items()} + if isinstance(value, A.MethodCall): + value.obj = rewrite(value.obj) + value.args = [rewrite(argument) for argument in value.args] + value.kwargs = [(name, rewrite(argument)) for name, argument in value.kwargs] + if isinstance(value.obj, A.Name) and value.obj.name in class_table: + symbol = ensure_function(value.obj.name, value.method) + if symbol is not None: + return A.Call( + func=symbol, + args=value.args, + kwargs=value.kwargs, + pos=value.pos, + ) + return value + if is_dataclass(value) and not isinstance(value, type): + for data_field in fields(value): + setattr(value, data_field.name, rewrite(getattr(value, data_field.name))) + return value + + mod.body = rewrite(mod.body) + for function in mod.funcs: + function.body = rewrite(function.body) + for owner in mod.classes: + for method in owner.methods: + method.body = rewrite(method.body) + + index = 0 + while index < len(pending_functions): + function = pending_functions[index] + index += 1 + function.body = rewrite(function.body) + mod.funcs.append(function) + + +def _analyze_with_direct_class_calls(self: SemaAnalyzer) -> None: + _lower_class_qualified_calls(self.mod) + _ORIGINAL_ANALYZE(self) + + +if not getattr(SemaAnalyzer, "_asmpython_direct_class_call_patch", False): + SemaAnalyzer.analyze = _analyze_with_direct_class_calls + SemaAnalyzer._asmpython_direct_class_call_patch = True From 6f664c7886ac4e943a24335ce9ea2a0ecf5f5228 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:07:21 -0500 Subject: [PATCH 28/53] Load direct static/class method call lowering --- asmpython/_compiler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/asmpython/_compiler/__init__.py b/asmpython/_compiler/__init__.py index 037a3a157..f3e4bdad2 100644 --- a/asmpython/_compiler/__init__.py +++ b/asmpython/_compiler/__init__.py @@ -24,5 +24,6 @@ from . import class_value_compat_fixes as _class_value_compat_fixes from . import dynamic_value_compat_fixes as _dynamic_value_compat_fixes from . import concrete_specialization_compat_fixes as _concrete_specialization_compat_fixes +from . import static_method_call_compat_fixes as _static_method_call_compat_fixes __all__ = ["__version__"] From f79fb47677116769fbe381faede9923c026f0b55 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:09:56 -0500 Subject: [PATCH 29/53] Expect Python boolean provider output --- tests/cases/468_provider_type_runtime.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/cases/468_provider_type_runtime.py b/tests/cases/468_provider_type_runtime.py index 1a3b08460..240beb0a2 100644 --- a/tests/cases/468_provider_type_runtime.py +++ b/tests/cases/468_provider_type_runtime.py @@ -1,13 +1,13 @@ # expect: -# 1 -# 0 -# 1 -# 1 -# 1 -# 1 +# True +# False +# True +# True +# True +# True # Finite class tuples must lower without a dynamic metatype runtime. # Dynamic values must preserve string behavior. -# Tuple-membership isolation generation 2. +# Python-boolean verification generation 1. class Provider: runtime_realms = ("server", "client") From f95982861467c77185cd2f8b1f21e47686d8a9a1 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:15:02 -0500 Subject: [PATCH 30/53] Add property-generator string flow regression --- tests/cases/469_property_generator_string.py | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/cases/469_property_generator_string.py diff --git a/tests/cases/469_property_generator_string.py b/tests/cases/469_property_generator_string.py new file mode 100644 index 000000000..53fca0813 --- /dev/null +++ b/tests/cases/469_property_generator_string.py @@ -0,0 +1,46 @@ +# expect: +# 2 +# somnia.Root + + +class Registry: + def __init__(self): + self.names = { + "Root": "somnia.Root", + "Child": "somnia.Child", + } + + def type_name(self, value): + return self.names.get( + value.name, + getattr(value, "fallback_name", value.name), + ) + + +REGISTRY = Registry() + + +class Node: + def __init__(self, name): + self.name = name + self.children = [] + + @property + def type_name(self): + return REGISTRY.type_name(self) + + def walk(self): + yield self + for child in self.children: + yield from child.walk() + + +root = Node("Root") +root.children.append(Node("Child")) +values = [ + obj.type_name + for obj in root.walk() + if obj.type_name.startswith("somnia.") +] +print(len(values)) +print(values[0]) From e803561eb4093bdc8f140520a2b6760a31109941 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:16:11 -0500 Subject: [PATCH 31/53] ci: run property-generator string regression --- tests/cases/469_property_generator_string.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/469_property_generator_string.py b/tests/cases/469_property_generator_string.py index 53fca0813..0de09678b 100644 --- a/tests/cases/469_property_generator_string.py +++ b/tests/cases/469_property_generator_string.py @@ -1,6 +1,7 @@ # expect: # 2 # somnia.Root +# Property-flow verification generation 1. class Registry: From 317086695fac68527c7e68337c789a13aacd12cf Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:18:32 -0500 Subject: [PATCH 32/53] ci: rerun property-flow diagnostic --- tests/cases/469_property_generator_string.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/469_property_generator_string.py b/tests/cases/469_property_generator_string.py index 0de09678b..27e0c62f0 100644 --- a/tests/cases/469_property_generator_string.py +++ b/tests/cases/469_property_generator_string.py @@ -1,7 +1,7 @@ # expect: # 2 # somnia.Root -# Property-flow verification generation 1. +# Property-flow verification generation 2. class Registry: From 6ed2334c491f4428374e13264b172c253a379dc6 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:25:58 -0500 Subject: [PATCH 33/53] Add Linux exception ABI shims --- .../_runtime/abi_exception_shims_linux.asm | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 asmpython/_runtime/abi_exception_shims_linux.asm diff --git a/asmpython/_runtime/abi_exception_shims_linux.asm b/asmpython/_runtime/abi_exception_shims_linux.asm new file mode 100644 index 000000000..4d8a183da --- /dev/null +++ b/asmpython/_runtime/abi_exception_shims_linux.asm @@ -0,0 +1,23 @@ +; SysV exception ABI shims for the built-in x86-64 backend. +; +; The runtime helpers use asmpython's internal rax/rbx convention, while the +; IR backend emits ordinary SysV calls with arguments in rdi/rsi. + +extern _runtime_setjmp +extern _runtime_raise + +global _abi_setjmp +global _abi_raise + +section .text + +; setjmp(buffer=rdi) -> runtime result +_abi_setjmp: + mov rax, rdi + jmp _runtime_setjmp + +; raise(message=rdi, exception_type_id=rsi) -> does not normally return +_abi_raise: + mov rax, rdi + mov rbx, rsi + jmp _runtime_raise From 12872556a52f07a172dbcd794fa9b6129714d093 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:26:12 -0500 Subject: [PATCH 34/53] Include Linux exception ABI shims --- asmpython/_runtime/abi_shims_linux_bundle.asm | 1 + 1 file changed, 1 insertion(+) diff --git a/asmpython/_runtime/abi_shims_linux_bundle.asm b/asmpython/_runtime/abi_shims_linux_bundle.asm index e4d853686..3dfd305bd 100644 --- a/asmpython/_runtime/abi_shims_linux_bundle.asm +++ b/asmpython/_runtime/abi_shims_linux_bundle.asm @@ -7,6 +7,7 @@ ; avoids baking a target choice into the shared IR. %include "abi_shims_linux.asm" +%include "abi_exception_shims_linux.asm" extern dlopen extern dlsym From 60be13bf27becd7f999c3b11cda996f288dfd4b7 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:28:37 -0500 Subject: [PATCH 35/53] Add direct string property regression --- tests/cases/470_property_string_only.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 tests/cases/470_property_string_only.py diff --git a/tests/cases/470_property_string_only.py b/tests/cases/470_property_string_only.py new file mode 100644 index 000000000..aca1ba70a --- /dev/null +++ b/tests/cases/470_property_string_only.py @@ -0,0 +1,26 @@ +# expect: +# somnia.Root + + +class Registry: + def __init__(self): + self.names = {"Root": "somnia.Root"} + + def type_name(self, value): + return self.names.get(value.name, value.name) + + +REGISTRY = Registry() + + +class Node: + def __init__(self, name): + self.name = name + + @property + def type_name(self): + return REGISTRY.type_name(self) + + +root = Node("Root") +print(root.type_name) From 0198377a26ebe9ced9ae01691cc6e57db3d8f7d2 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:28:51 -0500 Subject: [PATCH 36/53] Add recursive generator regression --- tests/cases/471_recursive_generator_only.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/cases/471_recursive_generator_only.py diff --git a/tests/cases/471_recursive_generator_only.py b/tests/cases/471_recursive_generator_only.py new file mode 100644 index 000000000..d373054f0 --- /dev/null +++ b/tests/cases/471_recursive_generator_only.py @@ -0,0 +1,17 @@ +# expect: +# 2 + + +class Node: + def __init__(self): + self.children = [] + + def walk(self): + yield self + for child in self.children: + yield from child.walk() + + +root = Node() +root.children.append(Node()) +print(len(root.walk())) From 6ae7dc99ed3689eb739ab9aef14922eecf1c9fdb Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:29:04 -0500 Subject: [PATCH 37/53] Add property comprehension regression --- tests/cases/472_property_comprehension_only.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 tests/cases/472_property_comprehension_only.py diff --git a/tests/cases/472_property_comprehension_only.py b/tests/cases/472_property_comprehension_only.py new file mode 100644 index 000000000..9101f3318 --- /dev/null +++ b/tests/cases/472_property_comprehension_only.py @@ -0,0 +1,18 @@ +# expect: +# 2 +# somnia.Root + + +class Node: + def __init__(self, type_name): + self.type_name = type_name + + +nodes = [Node("somnia.Root"), Node("somnia.Child")] +values = [ + node.type_name + for node in nodes + if node.type_name.startswith("somnia.") +] +print(len(values)) +print(values[0]) From 49d4cfc117c009d19b853997e09a1f18b3347ebe Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:30:22 -0500 Subject: [PATCH 38/53] ci: run isolated runtime probes --- tests/cases/469_property_generator_string.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/469_property_generator_string.py b/tests/cases/469_property_generator_string.py index 27e0c62f0..061b5aefa 100644 --- a/tests/cases/469_property_generator_string.py +++ b/tests/cases/469_property_generator_string.py @@ -1,7 +1,7 @@ # expect: # 2 # somnia.Root -# Property-flow verification generation 2. +# Property-flow verification generation 3. class Registry: From 6b4167c1df98d0d02289bfc6e0bbb7ad45040cb7 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:32:05 -0500 Subject: [PATCH 39/53] Add dict method string return regression --- tests/cases/473_dict_method_string.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 tests/cases/473_dict_method_string.py diff --git a/tests/cases/473_dict_method_string.py b/tests/cases/473_dict_method_string.py new file mode 100644 index 000000000..87bf2ec70 --- /dev/null +++ b/tests/cases/473_dict_method_string.py @@ -0,0 +1,19 @@ +# expect: +# somnia.Root + + +class Value: + def __init__(self, name): + self.name = name + + +class Registry: + def __init__(self): + self.names = {"Root": "somnia.Root"} + + def type_name(self, value): + return self.names.get(value.name, value.name) + + +registry = Registry() +print(registry.type_name(Value("Root"))) From 57aae1fe0895752c35d0ef7f8a06a057a33e4482 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:32:17 -0500 Subject: [PATCH 40/53] Add literal string property regression --- tests/cases/474_literal_property_string.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/cases/474_literal_property_string.py diff --git a/tests/cases/474_literal_property_string.py b/tests/cases/474_literal_property_string.py new file mode 100644 index 000000000..e6599d0ec --- /dev/null +++ b/tests/cases/474_literal_property_string.py @@ -0,0 +1,11 @@ +# expect: +# somnia.Root + + +class Node: + @property + def type_name(self): + return "somnia.Root" + + +print(Node().type_name) From e4caf1a597dbda9c5dbd01a263fd05921d85514c Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:33:34 -0500 Subject: [PATCH 41/53] ci: run method and property string probes --- tests/cases/470_property_string_only.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/470_property_string_only.py b/tests/cases/470_property_string_only.py index aca1ba70a..e963141d1 100644 --- a/tests/cases/470_property_string_only.py +++ b/tests/cases/470_property_string_only.py @@ -1,5 +1,6 @@ # expect: # somnia.Root +# Method/property isolation generation 1. class Registry: From 03067a9758e059a9c48a57ff8c977b2fabefeea4 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:35:38 -0500 Subject: [PATCH 42/53] ci: run dict method return diagnostic --- tests/cases/473_dict_method_string.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/473_dict_method_string.py b/tests/cases/473_dict_method_string.py index 87bf2ec70..74e69320c 100644 --- a/tests/cases/473_dict_method_string.py +++ b/tests/cases/473_dict_method_string.py @@ -1,5 +1,6 @@ # expect: # somnia.Root +# Dict-method diagnostic generation 1. class Value: From c50f7ac5a01727a161771e723aa28f83a9ac309e Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:39:21 -0500 Subject: [PATCH 43/53] Infer literal collection field elements and returns --- .../_compiler/container_field_compat_fixes.py | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/asmpython/_compiler/container_field_compat_fixes.py b/asmpython/_compiler/container_field_compat_fixes.py index 174d563ae..e00367c3a 100644 --- a/asmpython/_compiler/container_field_compat_fixes.py +++ b/asmpython/_compiler/container_field_compat_fixes.py @@ -24,6 +24,45 @@ def _annotation_parts(annotation) -> tuple[str, "str | None"]: return "", None +def _literal_kind(expression) -> "str | None": + if isinstance(expression, A.StrLit) or isinstance(expression, A.FString): + return "str" + if isinstance(expression, A.FloatLit): + return "float" + if isinstance(expression, A.IntLit): + if getattr(expression, "is_none", False): + return None + return "bool" if getattr(expression, "is_bool", False) else "int" + if isinstance(expression, A.ListLit): + return "list" + if isinstance(expression, A.DictLit): + return "dict" + if isinstance(expression, A.TupleLit): + return "tuple" + if isinstance(expression, A.SetLit): + return "set" + return None + + +def _literal_collection_element(expression, base: str) -> "str | None": + values = None + if base == "dict" and isinstance(expression, A.DictLit): + values = list(expression.values) + elif base == "list" and isinstance(expression, A.ListLit): + values = list(expression.elems) + if not values: + return None + kinds = [_literal_kind(value) for value in values] + if any(kind is None for kind in kinds): + return None + unique = set(kinds) + if len(unique) == 1: + return kinds[0] + if unique.issubset({"bool", "int", "float"}): + return "float" if "float" in unique else "int" + return "any" + + def _hierarchy_method_names(owner, classes: dict) -> set[str]: result: set[str] = set() current = owner @@ -101,7 +140,13 @@ def _collection_fields(mod: A.Module) -> dict[tuple[str, str], tuple[str, str]]: ): continue base, element = _annotation_parts(statement.annot) - if base not in ("list", "dict") or element is None: + if base not in ("list", "dict"): + continue + literal_element = _literal_collection_element(statement.value, base) + if literal_element is not None and element in (None, "any", "int"): + element = literal_element + statement.annot = (base, element) + if element is None: continue if base == "list" and element == "any": refined = _recursive_object_element(owner, statement.name, classes) @@ -114,6 +159,38 @@ def _collection_fields(mod: A.Module) -> dict[tuple[str, str], tuple[str, str]]: return result +def _refine_collection_method_returns(mod: A.Module, table: dict) -> None: + """Propagate concrete ``dict.get``/field return kinds into method signatures.""" + for owner in mod.classes: + for method in owner.methods: + observed: list[str] = [] + for statement in _walk_statements(method.body): + if not isinstance(statement, A.Return): + continue + value = statement.value + if ( + isinstance(value, A.MethodCall) + and value.method == "get" + and isinstance(value.obj, A.Attr) + and isinstance(value.obj.obj, A.Name) + and value.obj.obj.name == "self" + ): + metadata = table.get((owner.name, value.obj.name)) + if metadata is not None and metadata[0] == "dict": + observed.append(metadata[1]) + elif ( + isinstance(value, A.Attr) + and isinstance(value.obj, A.Name) + and value.obj.name == "self" + ): + metadata = table.get((owner.name, value.name)) + if metadata is not None: + observed.append(metadata[0]) + concrete = {kind for kind in observed if kind not in ("any", "int", "")} + if len(concrete) == 1: + method.ret_type = (next(iter(concrete)), None) + + def _lookup_field(self: SemaAnalyzer, receiver_type: str, field_name: str): table = getattr(self, "_compat_collection_fields", {}) if receiver_type.startswith("instance:"): @@ -175,6 +252,7 @@ def _analyze_with_collection_fields(self: SemaAnalyzer) -> None: _mark_dynamic_parameters(self.mod) _annotate_object_fields(self.mod) self._compat_collection_fields = _collection_fields(self.mod) + _refine_collection_method_returns(self.mod, self._compat_collection_fields) _ORIGINAL_ANALYZE(self) From cef6bb5767f8c4a6c1b8ba48bff9480cd671851c Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:41:59 -0500 Subject: [PATCH 44/53] Propagate returns through global singleton methods --- .../global_singleton_flow_compat_fixes.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 asmpython/_compiler/global_singleton_flow_compat_fixes.py diff --git a/asmpython/_compiler/global_singleton_flow_compat_fixes.py b/asmpython/_compiler/global_singleton_flow_compat_fixes.py new file mode 100644 index 000000000..6368a2344 --- /dev/null +++ b/asmpython/_compiler/global_singleton_flow_compat_fixes.py @@ -0,0 +1,131 @@ +"""Propagate method return types through module-global singleton objects. + +Whole-program projects commonly create a registry or manager once at module +scope and delegate properties/methods through it:: + + REGISTRY = Registry() + + @property + def type_name(self): + return REGISTRY.type_name(self) + +The concrete class of ``REGISTRY`` is statically known. Resolve such delegated +returns before semantic analysis so native callers use the returned value's +actual representation rather than formatting a string pointer as an integer. +""" + +from __future__ import annotations + +from . import ast_nodes as A +from .container_field_compat_fixes import ( + _collection_fields, + _refine_collection_method_returns, +) +from .dynamic_parameter_compat_fixes import _mark_dynamic_parameters +from .field_flow_compat_fixes import _annotate_object_fields +from .language_compat_fixes import _normalize_method_receivers +from .object_flow_compat_fixes import _walk_statements +from .sema import SemaAnalyzer + + +_ORIGINAL_ANALYZE = SemaAnalyzer.analyze + + +def _annotation_name(annotation) -> "str | None": + if isinstance(annotation, str): + return annotation + if isinstance(annotation, tuple) and annotation: + return annotation[0] if isinstance(annotation[0], str) else None + return None + + +def _global_singletons(mod: A.Module) -> dict[str, str]: + class_names = {owner.name for owner in mod.classes} + result: dict[str, str] = {} + for statement in mod.body: + if ( + isinstance(statement, A.Assign) + and isinstance(statement.target, str) + and isinstance(statement.value, A.Call) + and statement.value.func in class_names + ): + result[statement.target] = statement.value.func + return result + + +def _resolve_method_return( + class_name: str, + method_name: str, + classes: dict, +) -> "str | None": + current = class_name + seen: set[str] = set() + while current in classes and current not in seen: + seen.add(current) + owner = classes[current] + for method in owner.methods: + if method.name != method_name: + continue + value = _annotation_name(method.ret_type) + if value is not None: + return value + current = owner.parent + return None + + +def _refine_global_singleton_returns(mod: A.Module) -> None: + classes = {owner.name: owner for owner in mod.classes} + globals_by_name = _global_singletons(mod) + if not globals_by_name: + return + + # Delegation chains can be several calls deep. A short fixed point mirrors + # the compiler's ordinary unannotated-return inference pass. + for _iteration in range(12): + changed = False + for owner in mod.classes: + for method in owner.methods: + observed: set[str] = set() + for statement in _walk_statements(method.body): + if not isinstance(statement, A.Return): + continue + value = statement.value + if not ( + isinstance(value, A.MethodCall) + and isinstance(value.obj, A.Name) + and value.obj.name in globals_by_name + ): + continue + return_type = _resolve_method_return( + globals_by_name[value.obj.name], + value.method, + classes, + ) + if return_type not in (None, "any"): + observed.add(return_type) + if len(observed) != 1: + continue + inferred = next(iter(observed)) + current = _annotation_name(method.ret_type) + if current != inferred: + method.ret_type = (inferred, None) + changed = True + if not changed: + break + + +def _analyze_with_global_singleton_flow(self: SemaAnalyzer) -> None: + # Re-run the idempotent field passes here so their concrete collection + # metadata is available before resolving singleton delegation. + _normalize_method_receivers(self.mod) + _mark_dynamic_parameters(self.mod) + _annotate_object_fields(self.mod) + table = _collection_fields(self.mod) + _refine_collection_method_returns(self.mod, table) + _refine_global_singleton_returns(self.mod) + _ORIGINAL_ANALYZE(self) + + +if not getattr(SemaAnalyzer, "_asmpython_global_singleton_flow_patch", False): + SemaAnalyzer.analyze = _analyze_with_global_singleton_flow + SemaAnalyzer._asmpython_global_singleton_flow_patch = True From b69cb4ff5edb81387e0a88d88e3c341d3d78368e Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:42:33 -0500 Subject: [PATCH 45/53] Load global singleton return flow --- asmpython/_compiler/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/asmpython/_compiler/__init__.py b/asmpython/_compiler/__init__.py index f3e4bdad2..58ff02780 100644 --- a/asmpython/_compiler/__init__.py +++ b/asmpython/_compiler/__init__.py @@ -25,5 +25,6 @@ from . import dynamic_value_compat_fixes as _dynamic_value_compat_fixes from . import concrete_specialization_compat_fixes as _concrete_specialization_compat_fixes from . import static_method_call_compat_fixes as _static_method_call_compat_fixes +from . import global_singleton_flow_compat_fixes as _global_singleton_flow_compat_fixes __all__ = ["__version__"] From 04f7461a8b3cde63cd7db6acab68aa145df68da4 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:44:59 -0500 Subject: [PATCH 46/53] Order singleton flow after container inference --- asmpython/_compiler/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asmpython/_compiler/__init__.py b/asmpython/_compiler/__init__.py index 58ff02780..db77c41c2 100644 --- a/asmpython/_compiler/__init__.py +++ b/asmpython/_compiler/__init__.py @@ -16,6 +16,7 @@ from . import type_parameter_compat_fixes as _type_parameter_compat_fixes from . import field_flow_compat_fixes as _field_flow_compat_fixes from . import container_field_compat_fixes as _container_field_compat_fixes +from . import global_singleton_flow_compat_fixes as _global_singleton_flow_compat_fixes from . import live_definition_compat_fixes as _live_definition_compat_fixes from . import empty_collection_compat_fixes as _empty_collection_compat_fixes from . import ordered_flow_compat_fixes as _ordered_flow_compat_fixes @@ -25,6 +26,5 @@ from . import dynamic_value_compat_fixes as _dynamic_value_compat_fixes from . import concrete_specialization_compat_fixes as _concrete_specialization_compat_fixes from . import static_method_call_compat_fixes as _static_method_call_compat_fixes -from . import global_singleton_flow_compat_fixes as _global_singleton_flow_compat_fixes __all__ = ["__version__"] From 69c197da3eb773e5b15a9eded0d9cf7405c16e99 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:46:12 -0500 Subject: [PATCH 47/53] Add simple object generator regression --- tests/cases/475_simple_generator_self.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 tests/cases/475_simple_generator_self.py diff --git a/tests/cases/475_simple_generator_self.py b/tests/cases/475_simple_generator_self.py new file mode 100644 index 000000000..007730a57 --- /dev/null +++ b/tests/cases/475_simple_generator_self.py @@ -0,0 +1,10 @@ +# expect: +# 1 + + +class Node: + def walk(self): + yield self + + +print(len(Node().walk())) From 97b881ec150fde2326781578d3749f184d888c5e Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:46:23 -0500 Subject: [PATCH 48/53] Add child iteration generator regression --- tests/cases/476_generator_child_iteration.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 tests/cases/476_generator_child_iteration.py diff --git a/tests/cases/476_generator_child_iteration.py b/tests/cases/476_generator_child_iteration.py new file mode 100644 index 000000000..b6354fbd4 --- /dev/null +++ b/tests/cases/476_generator_child_iteration.py @@ -0,0 +1,17 @@ +# expect: +# 2 + + +class Node: + def __init__(self): + self.children = [] + + def walk(self): + yield self + for child in self.children: + yield child + + +root = Node() +root.children.append(Node()) +print(len(root.walk())) From c9ad2a166ba815ed85b509340f0d2ac4bd2eaec6 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:47:41 -0500 Subject: [PATCH 49/53] ci: run generator isolation probes --- tests/cases/471_recursive_generator_only.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/cases/471_recursive_generator_only.py b/tests/cases/471_recursive_generator_only.py index d373054f0..7458bab8c 100644 --- a/tests/cases/471_recursive_generator_only.py +++ b/tests/cases/471_recursive_generator_only.py @@ -1,5 +1,6 @@ # expect: # 2 +# Generator isolation generation 1. class Node: From 2adc8e7806504285bfb2c49db280c9a3868a61fd Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:50:10 -0500 Subject: [PATCH 50/53] ci: run recursive generator diagnostic --- tests/cases/471_recursive_generator_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/471_recursive_generator_only.py b/tests/cases/471_recursive_generator_only.py index 7458bab8c..d699a1b17 100644 --- a/tests/cases/471_recursive_generator_only.py +++ b/tests/cases/471_recursive_generator_only.py @@ -1,6 +1,6 @@ # expect: # 2 -# Generator isolation generation 1. +# Generator isolation generation 2. class Node: From 1157581c6c1aeabf3d90eee430ffb686451b4823 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:53:28 -0500 Subject: [PATCH 51/53] Infer collection fields from module mutations --- .../_compiler/container_field_compat_fixes.py | 109 +++++++++++++++++- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/asmpython/_compiler/container_field_compat_fixes.py b/asmpython/_compiler/container_field_compat_fixes.py index e00367c3a..951204f52 100644 --- a/asmpython/_compiler/container_field_compat_fixes.py +++ b/asmpython/_compiler/container_field_compat_fixes.py @@ -125,6 +125,109 @@ def _cross_object_self_element(owner, field_name: str) -> "str | None": return None +def _common_class(left: str, right: str, classes: dict) -> str: + if left == right: + return left + left_chain: list[str] = [] + current = left + seen: set[str] = set() + while current in classes and current not in seen: + seen.add(current) + left_chain.append(current) + current = classes[current].parent + right_chain: set[str] = set() + current = right + seen = set() + while current in classes and current not in seen: + seen.add(current) + right_chain.add(current) + current = classes[current].parent + for candidate in left_chain: + if candidate in right_chain: + return candidate + return "any" + + +def _module_instance_types(mod: A.Module, classes: dict) -> dict[str, str]: + result: dict[str, str] = {} + for statement in mod.body: + if ( + isinstance(statement, A.Assign) + and isinstance(statement.target, str) + and isinstance(statement.value, A.Call) + and statement.value.func in classes + ): + result[statement.target] = statement.value.func + return result + + +def _set_initializer_element( + classes: dict, + owner_name: str, + field_name: str, + element: str, +) -> None: + owner = classes.get(owner_name) + if owner is None: + return + for method in owner.methods: + if method.name != "__init__": + continue + for statement in method.body: + if ( + isinstance(statement, A.AttrAssign) + and isinstance(statement.obj, A.Name) + and statement.obj.name == "self" + and statement.name == field_name + ): + base, _old_element = _annotation_parts(statement.annot) + if base == "list": + statement.annot = ("list", element) + return + + +def _refine_module_collection_mutations( + mod: A.Module, + result: dict[tuple[str, str], tuple[str, str]], + classes: dict, +) -> None: + """Infer field element types from whole-program ``obj.field.append(value)``.""" + instances = _module_instance_types(mod, classes) + if not instances: + return + for node in _walk_statements(mod.body): + if not ( + isinstance(node, A.MethodCall) + and node.method == "append" + and len(node.args) == 1 + and isinstance(node.obj, A.Attr) + and isinstance(node.obj.obj, A.Name) + ): + continue + receiver_class = instances.get(node.obj.obj.name) + if receiver_class is None: + continue + argument = node.args[0] + element_class = None + if isinstance(argument, A.Call) and argument.func in classes: + element_class = argument.func + elif isinstance(argument, A.Name): + element_class = instances.get(argument.name) + if element_class is None: + continue + key = (receiver_class, node.obj.name) + metadata = result.get(key) + if metadata is None or metadata[0] != "list": + continue + previous = metadata[1] + if previous in ("any", "int", ""): + refined = element_class + else: + refined = _common_class(previous, element_class, classes) + result[key] = ("list", refined) + _set_initializer_element(classes, receiver_class, node.obj.name, refined) + + def _collection_fields(mod: A.Module) -> dict[tuple[str, str], tuple[str, str]]: result: dict[tuple[str, str], tuple[str, str]] = {} classes = {owner.name: owner for owner in mod.classes} @@ -156,6 +259,7 @@ def _collection_fields(mod: A.Module) -> dict[tuple[str, str], tuple[str, str]]: element = refined statement.annot = ("list", element) result[(owner.name, statement.name)] = (base, element) + _refine_module_collection_mutations(mod, result, classes) return result @@ -206,12 +310,9 @@ def _lookup_field(self: SemaAnalyzer, receiver_type: str, field_name: str): return None if receiver_type == "any": - # Dynamic Python receivers still carry useful structural information. - # When every class declaring this field agrees on its collection shape, - # that shape is safe to propagate without guessing the receiver class. candidates = { value - for (owner_name, candidate_name), value in table.items() + for (_owner_name, candidate_name), value in table.items() if candidate_name == field_name } if len(candidates) == 1: From e43b4b70f95002f2f9419b5ef2ba17d742d66ce7 Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:54:16 -0500 Subject: [PATCH 52/53] ci: verify inferred recursive generator elements --- tests/cases/471_recursive_generator_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/471_recursive_generator_only.py b/tests/cases/471_recursive_generator_only.py index d699a1b17..65fd4a68c 100644 --- a/tests/cases/471_recursive_generator_only.py +++ b/tests/cases/471_recursive_generator_only.py @@ -1,6 +1,6 @@ # expect: # 2 -# Generator isolation generation 2. +# Generator isolation generation 3. class Node: From 98010e12359346a0eaaadb79abdd500ad11db2db Mon Sep 17 00:00:00 2001 From: deltathedumb <133447617+deltathedumb@users.noreply.github.com> Date: Mon, 20 Jul 2026 20:55:43 -0500 Subject: [PATCH 53/53] ci: trigger recursive element verification --- tests/cases/471_recursive_generator_only.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/471_recursive_generator_only.py b/tests/cases/471_recursive_generator_only.py index 65fd4a68c..4c2f87e79 100644 --- a/tests/cases/471_recursive_generator_only.py +++ b/tests/cases/471_recursive_generator_only.py @@ -1,6 +1,6 @@ # expect: # 2 -# Generator isolation generation 3. +# Generator isolation generation 4. class Node: