Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
cd2e916
Add provider runtime type regression
deltathedumb Jul 20, 2026
e3a9a0e
Record provider runtime compiler inspection
github-actions[bot] Jul 20, 2026
6cb83dd
Trigger provider runtime type verifier
deltathedumb Jul 20, 2026
5887b0e
Record provider runtime compiler hooks
github-actions[bot] Jul 20, 2026
12acd92
Run provider runtime semantic probe
deltathedumb Jul 20, 2026
c8c40cd
Run provider runtime semantic probe on beta workflow
deltathedumb Jul 20, 2026
c4baa61
Trigger provider runtime PR verifier
deltathedumb Jul 20, 2026
f23bf6c
Run structural provider class probe
deltathedumb Jul 20, 2026
9422d28
Trigger indexed provider runtime verifier
deltathedumb Jul 21, 2026
0d36fab
Add finite class-value lowering pass
deltathedumb Jul 21, 2026
6726b16
Load finite class-value lowering
deltathedumb Jul 21, 2026
567d3fa
Focus provider type runtime regression
deltathedumb Jul 21, 2026
21a10f5
Document finite class tuple regression
deltathedumb Jul 21, 2026
155cba1
ci: rerun provider runtime diagnostic
deltathedumb Jul 21, 2026
4b1b5cf
Materialize inherited class methods for finite class values
deltathedumb Jul 21, 2026
59006d9
ci: rerun Somnia provider parity diagnostic
deltathedumb Jul 21, 2026
c9bbbdf
Infer dynamic value returns and stringify class objects
deltathedumb Jul 21, 2026
07b9303
Load dynamic value compatibility fixes
deltathedumb Jul 21, 2026
c7799e2
Test static class string conversion
deltathedumb Jul 21, 2026
8705250
ci: verify exact provider regression output
deltathedumb Jul 21, 2026
9b16390
Specialize inherited classmethods and concrete call parameters
deltathedumb Jul 21, 2026
aa5ec7b
Load concrete whole-program specializations
deltathedumb Jul 21, 2026
47be24f
Fold concrete class variables in specialized classmethods
deltathedumb Jul 21, 2026
ef78fcb
ci: inspect specialized provider AST
deltathedumb Jul 21, 2026
5721d78
Isolate tuple membership and static method typing
deltathedumb Jul 21, 2026
470b9f5
ci: run isolated membership regression
deltathedumb Jul 21, 2026
24d7f2f
Lower static and class method calls to direct functions
deltathedumb Jul 21, 2026
6f664c7
Load direct static/class method call lowering
deltathedumb Jul 21, 2026
f79fb47
Expect Python boolean provider output
deltathedumb Jul 21, 2026
f959828
Add property-generator string flow regression
deltathedumb Jul 21, 2026
e803561
ci: run property-generator string regression
deltathedumb Jul 21, 2026
3170866
ci: rerun property-flow diagnostic
deltathedumb Jul 21, 2026
6ed2334
Add Linux exception ABI shims
deltathedumb Jul 21, 2026
1287255
Include Linux exception ABI shims
deltathedumb Jul 21, 2026
60be13b
Add direct string property regression
deltathedumb Jul 21, 2026
0198377
Add recursive generator regression
deltathedumb Jul 21, 2026
6ae7dc9
Add property comprehension regression
deltathedumb Jul 21, 2026
49d4cfc
ci: run isolated runtime probes
deltathedumb Jul 21, 2026
6b4167c
Add dict method string return regression
deltathedumb Jul 21, 2026
57aae1f
Add literal string property regression
deltathedumb Jul 21, 2026
e4caf1a
ci: run method and property string probes
deltathedumb Jul 21, 2026
03067a9
ci: run dict method return diagnostic
deltathedumb Jul 21, 2026
c50f7ac
Infer literal collection field elements and returns
deltathedumb Jul 21, 2026
cef6bb5
Propagate returns through global singleton methods
deltathedumb Jul 21, 2026
b69cb4f
Load global singleton return flow
deltathedumb Jul 21, 2026
04f7461
Order singleton flow after container inference
deltathedumb Jul 21, 2026
69c197d
Add simple object generator regression
deltathedumb Jul 21, 2026
97b881e
Add child iteration generator regression
deltathedumb Jul 21, 2026
c9ad2a1
ci: run generator isolation probes
deltathedumb Jul 21, 2026
2adc8e7
ci: run recursive generator diagnostic
deltathedumb Jul 21, 2026
1157581
Infer collection fields from module mutations
deltathedumb Jul 21, 2026
e43b4b7
ci: verify inferred recursive generator elements
deltathedumb Jul 21, 2026
98010e1
ci: trigger recursive element verification
deltathedumb Jul 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions asmpython/_compiler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@
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
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
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__"]
255 changes: 255 additions & 0 deletions asmpython/_compiler/class_value_compat_fixes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
"""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 also materializes inherited class/static methods on those concrete
classes so backend symbol emission preserves Python's subclass ``cls`` value.
"""

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 _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
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
_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, {})
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
Loading