Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 19 additions & 0 deletions IMPLEMENTATION_PLAN_INTERPRETER.md
Original file line number Diff line number Diff line change
Expand Up @@ -796,3 +796,22 @@ matrix executions retain their existing scope. The unchanged console source
totals 372,607 bytes (+62); its
[diagnostic payload record](docs/measurements/2026-10-22-soft-keyword.json) retains
all hashes and preceding measurements. No execution feature is added.


### Generator and object-protocol checkpoint (2026-10-26)

The finite G1/G2 and O1/O2 contracts add owned suspended generator functions,
yield-from delegation, next/send/throw/close, isolated comprehensions, descriptor
attribute reflection and binary/reflected/in-place arithmetic. Both source
profiles keep their explicit selection and bytecode 4/5; class protocols remain
in classes-source/1. Node/Chromium draining and stepping, resource interruptions,
collection between instructions and offline source/Stop/Restart/update acceptance
are required. Existing composed resource baselines remain unchanged.

See [generators](docs/interpreter-generators.md) and
[object protocols](docs/interpreter-object-protocols.md) for exact signatures,
reference cases and remaining boundaries. Construction, super, richer type
relations, metaclasses, builtin subclasses, custom attribute hooks, dynamic
source, comprehension assignment expressions and async remain separately gated.
The broader #120/#123 and #33/#34 milestones remain open; this checkpoint does
not select a publication, deployment, dependency or host capability.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@ memoryviews. Run `uv run python -m examples.interpreter_values`, or add `--core`
for primitive keys. Broader set methods, bytearray resizing, buffer casts and
native buffers retain explicit future boundaries.

The [generator profile](docs/interpreter-generators.md) adds owned suspended
functions, yield-from delegation, next/send/throw/close and scoped list/dict/set/
generator comprehensions in both profiles. [Object protocols](docs/interpreter-object-protocols.md)
add attribute reflection and forward/reflected/in-place arithmetic in the class
profile. Run `python -m examples.interpreter_generators`, adding `--core` for
generators without classes. Construction, richer reflection and async retain
separate contracts.

The [continuing interpreter roadmap](docs/interpreter-roadmap.md) targets broad
Python 3.14+ compatibility through finite supported profiles. Execution ownership
is consolidated; the [coverage inventory](docs/interpreter-compatibility.md)
Expand Down
27 changes: 17 additions & 10 deletions docs/interpreter-compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -422,9 +422,9 @@
},
{
"id": "further-object-protocols",
"status": "deferred",
"scope": "Arithmetic/reflected/in-place slots, __new__, metaclasses, custom attribute hooks and builtin subclassing.",
"gaps": "Each slice requires an adopted reference and owned continuation contract.",
"status": "partial",
"scope": "Binary/reflected/in-place arithmetic, unary +,-,~, divmod and power slots through owned descriptor callbacks, with subclass priority and NotImplemented fallback.",
"gaps": "Construction, super, richer type relations, metaclasses, custom attribute hooks, builtin subclassing and broader reflection remain separate contracts.",
"reference_version": "3.14.7",
"targets": [
"node",
Expand All @@ -437,7 +437,10 @@
"metaclass",
"builtin-super"
],
"evidence": []
"evidence": [
"tests/fixtures/interpreter/object-protocols.json:subclass-priority",
"tests/fixtures/interpreter/object-protocols.json:inplace-fallback"
]
},
{
"id": "exceptions",
Expand Down Expand Up @@ -1122,8 +1125,8 @@
{
"id": "divmod",
"status": "partial",
"scope": "Positional-only primitive int/float quotient and remainder with owned tuple results and Python sign/zero rules.",
"gaps": "General user arithmetic/reflected slots remain INT-OBJECT-01.",
"scope": "Positional-only primitive int/float quotient and remainder with owned tuple results and Python sign/zero rules. Adopted user forward/reflected slots use owned descriptor dispatch.",
"gaps": "Further object construction/reflection remain separately gated.",
"reference_version": "3.14.7",
"targets": [
"node",
Expand All @@ -1135,7 +1138,9 @@
"probes": [
"builtin-divmod"
],
"evidence": []
"evidence": [
"tests/fixtures/interpreter/object-protocols.json:power-divmod"
]
},
{
"id": "enumerate",
Expand Down Expand Up @@ -1721,8 +1726,8 @@
{
"id": "pow",
"status": "partial",
"scope": "Two-argument real/integer power, modular integer power/inverse, and complex integer powers from -100 to 100 with finite intermediates; named base/exp/mod keywords.",
"gaps": "Remaining complex power/recovery families are INT-NUM-02; user arithmetic slots are INT-OBJECT-01.",
"scope": "Two-argument real/integer power, modular integer power/inverse, and complex integer powers from -100 to 100 with finite intermediates; named base/exp/mod keywords. Adopted user forward/reflected slots use owned descriptor dispatch.",
"gaps": "Remaining complex power/recovery families are INT-NUM-02; User-valued power modulus and remaining construction/reflection remain separately gated.",
"reference_version": "3.14.7",
"targets": [
"node",
Expand All @@ -1735,7 +1740,9 @@
"probes": [
"builtin-pow"
],
"evidence": []
"evidence": [
"tests/fixtures/interpreter/object-protocols.json:power-divmod"
]
},
{
"id": "print",
Expand Down
4 changes: 2 additions & 2 deletions docs/interpreter-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ issue families; availability alone never certifies an entire type or module.
| indexed-user-objects | partial | __index__/__getitem__/__setitem__/__delitem__ through current type lookup and owned callbacks; raw scalar/tuple/slice keys, builtin sequence conversion, __len__ index results and bounded list/tuple RHS slice mutation. Other iterable RHS slice assignment, slice repr/hash/indices, builtin subclasses and broader numeric/sequence APIs remain deferred. |
| iterated-user-objects | partial | Owned __iter__/__next__/__contains__, indexed fallback, iter/next signatures and iterator membership search in classes-source/1. Generators/async protocols, broader iterable-consuming APIs and ordering remain separate. |
| user-equality-hash | partial | Owned __eq__/__ne__/__hash__, NotImplemented, reflected priority, bounded container equality and user dictionary keys. Structural dictionary mutation during comparison, tuples containing user instances, bound-method/slice hashing and ordering remain separately gated. |
| further-object-protocols | deferred | Arithmetic/reflected/in-place slots, __new__, metaclasses, custom attribute hooks and builtin subclassing. Each slice requires an adopted reference and owned continuation contract. |
| further-object-protocols | partial | Owned binary/reflected/in-place and unary arithmetic slots, divmod and power. Construction, super, metaclasses, builtin subclasses and custom attribute hooks remain separate. |
| exceptions | partial | Adopted builtin exception classes, catch/raise/bare reraise, try/except/else/finally, active context and source tracebacks. Custom exception inheritance, explicit causes/suppression and exception groups/except* remain deferred. |
| imports | partial | Static absolute/relative imports from a finite granted source registry, packages, identity/cache, initialization order, cycles and failed-init retry. No ambient filesystem/sys.path/PyPI loader, dynamic __import__ or import hooks. Missing grants raise IMPORT/ImportError; that is an authority boundary, not proof of a missing language API. |
| generators-comprehensions | partial | Owned generator functions, yield/yield from, next/send/throw/close and shared budgets. Scoped list/dict/set/generator comprehensions are implemented; async/reflection and comprehension assignment expressions remain separate stages. |
| context-patterns | deferred | with and synchronous context manager protocols; match/case patterns and guards. No admitted source execution. Async context managers are a separate async gate. |
| annotations-generics | deferred | Python 3.14 annotation timing, type parameters, type aliases and required generic aliases. Annotations and generic syntax are not adopted. Recognized type aliases receive UNSUPPORTED without execution support. |
| formatted-strings | deferred | f-strings and formatting grammar; t-strings, Template/Interpolation and string.templatelib APIs. Tokenizer rejects these literal forms. Server-side t-string use by a consumer does not establish browser interpreter support. |
| async-language | deferred | Native coroutine/await semantics, async iteration/generators/context managers and bounded scheduling/cancellation. Owned instruction stepping is implemented, but multiple parked owners, persistent events and native coroutine syntax are not. Host Promises are not Python coroutines. |
| dynamic-code-reflection | deferred | compile/eval/exec, dynamic imports and broader globals/locals/type/attribute reflection. Source admission, namespace/identity and target authority need separate explicit contracts. |
| dynamic-code-reflection | partial | getattr/setattr/delattr/hasattr through owned class descriptors. Dynamic source, namespaces and broader reflection remain deferred. |
| selected-library-apis | partial | core-only/1 has no library modules; operator/1 includes exactly add, sub, mul, floordiv, mod, eq, lt, neg, not_, truth, getitem from own Python source. No whole-stdlib claim. json/math/random and licensed pure-Python colorsys API pilots require qualification. collections/itertools/functools/dataclasses/typing remain later API-selection decisions. |
| real-package-certification | deferred | A pinned package and exact entry-point use with source/resource/license closure, original/reduced differential execution and offline checks. No general package certification. Shimify reachability is not interpreter compatibility; spatial_ui/expose_api and compiler integrations retain consumer-specific contracts. |
| browser-capabilities | host-restricted | Offline browser worker with explicit source/assets/import grants; no ambient Python process/filesystem/network/device capabilities. Durable sessions/storage, event/view adapters and one bounded awaited host capability remain under INT-10. Device inference needs separate real-device evidence in APP-MODEL-01. |
Expand Down
15 changes: 15 additions & 0 deletions docs/interpreter-generators.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,18 @@ roots and external leases. These logical observations make no host RSS claim.

No new host capability, runtime dependency, publication or deployment is included.
See the [yield expression and generator method reference](https://docs.python.org/3.14/reference/expressions.html#yield-expressions).

## Acceptance

The corpus contains 28 adopted class comparisons and three explicit refusals;
the shared core selection has 25 comparisons and three refusals. CI runs all
eight target/profile/driver combinations and requires zero logical teardown.
Dedicated tests collect between every instruction, retain suspended aliases and
cycles, and interrupt execution in generator bodies and comprehension callbacks.
The existing eight composed resource baselines remain unchanged.

Run `python -m examples.interpreter_generators` to build a composed console;
`--core` selects generators without class protocols. Offline acceptance covers
new source after reload, method/body errors, hard Stop/Restart and waiting
class-to-core updates. Assignment expressions in comprehensions, multi-argument
throw, generator frame introspection and async retain their future boundaries.
19 changes: 17 additions & 2 deletions docs/interpreter-object-protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

INT-OBJECT-01 adopts finite slices against CPython 3.14.7. The authored
`object-protocols.json` reference must also execute independently in Node and
Chromium, draining and instruction-boundary drivers. O1 is implemented; O2 is the next stage. Attribute names can be arbitrary strings
Chromium, draining and instruction-boundary drivers. O1 and O2 are implemented. Attribute names can be arbitrary strings
within the 256-code-unit name quota. Classes remain explicit in
classes-source/1; core-source/1 keeps primitive arithmetic and generator support.

| Stage | Finite contract |
| --- | --- |
| O1 | getattr(object, name[, default]), hasattr(object, name), setattr(object, name, value), delattr(object, name) on admitted class attributes. Existing descriptor precedence, Python callbacks and metadata rules apply. Names must be strings; only AttributeError is suppressed by defaults/hasattr. No lexical mangling is applied to dynamic strings. |
| O2 | Binary/reflected/in-place slots for +, -, *, /, //, %, **, shifts and bitwise operators; unary +, -, ~; divmod and two/three-argument pow. Strict right-subclass reflected overrides have priority, same-type reflected fallback is suppressed, NotImplemented advances dispatch, and in-place slots fall back to binary dispatch. Results can be arbitrary owned Python values. |
| Future | __new__, super, richer type/isinstance/issubclass, builtin subclasses, metaclasses, custom attribute hooks, ordering, dir/vars/globals/locals/id, and dynamic exec/eval/compile/import remain independently gated. Matrix multiplication requires a separate source/operator slice. |
| Future | __new__, super, richer type/isinstance/issubclass, builtin subclasses, metaclasses, custom attribute hooks, ordering, dir/vars/globals/locals/id, and dynamic exec/eval/compile/import remain independently gated. Matrix multiplication, user-valued power modulus dispatch and ordinary list/tuple/text repetition require separate slices. Existing bytes repetition and list in-place repetition accept the adopted index hook. |

Implicit operations consult type/MRO slots and descriptor binding, ignoring
instance shadow attributes. Signature and callback failures propagate; a method
Expand All @@ -25,3 +25,18 @@ This delivery does not complete INT-OBJECT-01, INT-09, native extensions or asyn
host integration. No publication, deployment, service or dependency is selected.
See [numeric emulation](https://docs.python.org/3.14/reference/datamodel.html#emulating-numeric-types)
and [attribute builtins](https://docs.python.org/3.14/library/functions.html#getattr).

## Acceptance

The corpus contains 44 adopted comparisons and two explicit construction/hook
refusals. CI runs Node and Chromium through draining and instruction-boundary
drivers. Dedicated tests cover all twelve binary/reflected/in-place operator
families, unary hooks, subclass priority, descriptor binding, sequence index
fallback, source errors, recursion/work/allocation limits, cancellation and
collection between instructions. All continuations retain complete operand and
outer-destination roots; logical teardown must be zero.

The generator console composes these protocols with suspended source functions
and comprehensions. Offline acceptance includes freshly entered source, callback
errors and Stop/Restart. INT-OBJECT-01 remains open for its construction, super,
type-relation, metaclass, builtin-subclass and broader reflection slices.
63 changes: 63 additions & 0 deletions examples/interpreter_generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Build a checkout console demonstrating generators and object protocols."""

import argparse
from pathlib import Path

from examples.interpreter_console import write_source
from pocket_python.interpreter import build_interpreter, read_interpreter

ROOT = Path(__file__).resolve().parents[1]
CORE_SOURCE = '''def squares(limit):
try:
for number in range(limit):
yield number * number
finally:
print("finished")

odd = [number for number in squares(5) if number % 2]
print("odd squares", len(odd), odd[0], odd[1])
stream = (number + 10 for number in range(3))
print("stream", next(stream), stream.send(None))
stream.close()

def child():
value = yield 2
return value + 3

def parent():
result = yield from child()
yield result

stream = parent()
print("delegation", next(stream), stream.send(4))
stream.close()
'''
SOURCE = CORE_SOURCE + '''
class Counter:
def __init__(self, value): self.value = value
def __add__(self, other): return Counter(self.value + other)
def __radd__(self, other): return self + other
def __iadd__(self, other):
self.value += other
return self
@property
def label(self): return str(self.value)

counter = Counter(4)
alias = counter
counter += 3
print("counter", counter is alias, getattr(counter, "label"), (2 + counter).value)
print("attributes", hasattr(counter, "missing"), getattr(counter, "missing", 9))
'''

if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--source', type=Path, default=Path('build/generators-console-source'))
parser.add_argument('--output', type=Path, default=Path('build/generators-console'))
parser.add_argument('--core', action='store_true', help='Use generators without class protocols.')
args = parser.parse_args()
receipt = build_interpreter(write_source(args.source, source=CORE_SOURCE if args.core else SOURCE),
read_interpreter(ROOT / 'runtime'), args.output,
language_profile='core-source/1' if args.core else 'classes-source/1',
title='Python Generators Console')
print(f"Created {args.output.resolve()} ({receipt['size']['artifact_bytes']} bytes)")
2 changes: 1 addition & 1 deletion runtime/browser-host.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test("explicit class identity selects v5, composes libraries and keeps each run
await app.run('import operator\nclass A:\n def __init__(self,n): self.n=n\n def add(self,x): return operator.add(self.n,x)\nprint(A(7).add(5))\n');
assert.equal(output,'12\n');
await assert.rejects(app.run('A()'),error=>error.name==='NameError');
await assert.rejects(app.run("print('never')\nclass A:\n def __add__(self,other): return 7\n"),error=>error.code==='UNSUPPORTED');
await assert.rejects(app.run("print('never')\nclass A:\n def __matmul__(self,other): return 7\n"),error=>error.code==='UNSUPPORTED');
assert.equal(output,'12\n');await app.run('class B: pass\nprint(type(B()) is B)\n');
assert.equal(output,'12\nTrue\n');assert.equal(app.stats().pending,0);
}finally{app.stop();}
Expand Down
5 changes: 5 additions & 0 deletions runtime/class-policy.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// Admission is separate from descriptor binding and scoped metadata validation.
export const DESCRIPTOR_HOOKS = Object.freeze(["__get__", "__set__", "__delete__", "__set_name__"]);
export const ARITHMETIC_SLOTS = Object.freeze({add: "add", subtract: "sub", multiply: "mul", divide: "truediv", "floor-divide": "floordiv",
modulo: "mod", power: "pow", "bit-and": "and", "bit-or": "or", "bit-xor": "xor", "left-shift": "lshift", "right-shift": "rshift", divmod: "divmod"});
const contexts = new Set(["define", "attribute", "mutate", "implicit"]);
const ordinarySlot = Object.freeze({define: true, attribute: true, mutate: true, implicit: false});
export const CLASS_SPECIAL_POLICY = Object.freeze(Object.fromEntries([
...["__call__", "__len__", "__bool__", "__repr__", "__str__", "__index__", "__bytes__", "__getitem__", "__setitem__", "__delitem__", "__iter__", "__next__", "__contains__", "__eq__", "__ne__", "__hash__", "__int__", "__float__", "__complex__", "__abs__", "__round__"].map(name => [name, Object.freeze({...ordinarySlot, implicit: true})]),
...Object.values(ARITHMETIC_SLOTS).flatMap(stem => ["", "r", ...(stem === "divmod" ? [] : ["i"])].map(prefix =>
["__" + prefix + stem + "__", Object.freeze({...ordinarySlot, implicit: true})])),
...["__neg__", "__pos__", "__invert__"].map(name => [name, Object.freeze({...ordinarySlot, implicit: true})]),
["__init__", ordinarySlot], ...DESCRIPTOR_HOOKS.map(name => [name, ordinarySlot]),
["__module__", Object.freeze({define: true})], ["__qualname__", Object.freeze({define: true})]
]));
Expand Down
Loading
Loading