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
21 changes: 12 additions & 9 deletions docs/interpreter-compatibility.json
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,8 @@
{
"id": "class-construction",
"status": "partial",
"scope": "Explicit classes-source/1 only: user classes, bounded C3 MRO, __init__, ordinary attributes/deletion and bound methods.",
"gaps": "Builtin subclassing, __new__, super, metaclasses, implicit __class__ cells and full reflective behavior are not adopted. Builtin list/tuple bases are explicitly refused with UNSUPPORTED.",
"scope": "Explicit classes-source/1: user classes, bounded C3 MRO, owned __new__/__init__, conditional runtime-type initialization, descriptors, bound methods, lexical __class__ cells and explicit/zero-argument super.",
"gaps": "Metaclasses, builtin instance subclassing and broader type/reflection remain separate construction stages.",
"reference_version": "3.14.7",
"targets": [
"node",
Expand All @@ -253,9 +253,8 @@
"metaclass"
],
"evidence": [
"tests/fixtures/interpreter/classes.json:diamond-mro",
"tests/fixtures/interpreter/classes.json:class-body-order",
"tests/fixtures/interpreter/classes.json:implicit-class-cell"
"tests/fixtures/interpreter/construction.json:new-subclass-init",
"tests/fixtures/interpreter/construction.json:super-diamond"
]
},
{
Expand Down Expand Up @@ -1986,9 +1985,9 @@
},
{
"id": "super",
"status": "deferred",
"scope": "Not installed; resolving this builtin name raises Python NameError.",
"gaps": "No implementation/API support credit from the negative availability probe.",
"status": "partial",
"scope": "Explicit bound/unbound and zero-argument super with lexical class cells, current C3 lookup, descriptors and proxy metadata in classes-source/1.",
"gaps": "Builtin instance layouts, custom __class__ hooks and proxy reinitialization remain separate.",
"reference_version": "3.14.7",
"targets": [
"node",
Expand All @@ -2000,7 +1999,11 @@
"probes": [
"builtin-super"
],
"evidence": []
"evidence": [
"tests/fixtures/interpreter/construction.json:super-diamond",
"tests/fixtures/interpreter/construction.json:super-unbound",
"tests/fixtures/interpreter/construction.json:super-generator"
]
},
{
"id": "tuple",
Expand Down
4 changes: 2 additions & 2 deletions docs/interpreter-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ issue families; availability alone never certifies an entire type or module.
| ordinary-control | supported | if/elif/else, while/for over adopted values, loop else/break/continue, return and finally control transfer. Generator, context-manager, match and async control are separate rows. |
| function-binding | partial | def, recursion, positional-only/ordinary/keyword-only parameters; literal and definition-time computed/mutable defaults and lexical closures. Global/nonlocal writes and deletion share the defining module and lexical cells. Variadic positional/keyword parameters pack fresh owned values. Positional starred calls consume owned iterables. Keyword call expansion admits owned dictionaries and keys/getitem mappings with ordered duplicate checks. Construction-hint forms and annotations remain deferred; signatures and error-message comparisons are scoped by interpreter-function-binding.md. |
| ordinary-syntax-gaps | partial | Assertions and assignment expressions in both source profiles, with owned truth callbacks and lexical scope writes. Lambda functions use ordinary parameters/defaults and lexical cells. Tuple/list/starred assignments, loop targets, and starred sequence/dictionary displays use owned iteration/mapping phases. Construction hints remain with their sequence task; assertion message formatting and comprehensions retain separate gates. |
| class-construction | partial | Explicit classes-source/1 only: user classes, bounded C3 MRO, __init__, ordinary attributes/deletion and bound methods. Builtin subclassing, __new__, super, metaclasses, implicit __class__ cells and full reflective behavior are not adopted. Builtin list/tuple bases are explicitly refused with UNSUPPORTED. |
| class-construction | partial | Explicit classes-source/1: user classes, bounded C3 MRO, owned __new__/__init__, conditional runtime-type initialization, descriptors, bound methods, lexical __class__ cells and explicit/zero-argument super. Metaclasses, builtin instance subclassing and broader reflection retain their separate construction stages. |
| private-names | supported | Lexical private-name mangling in admitted class bodies, nested code, parameters/imports and metadata. No additional syntax beyond the explicit source profile. |
| descriptors-decorators | partial | Functions, properties, static/class methods, custom get/set/delete/name-notification descriptors and decorators in classes-source/1. Descriptor-valued setter/deleter/name-notification hooks and unrestricted reflection remain refused. |
| call-length-truth | supported | Type-level __call__/__len__/__bool__, callable(), len(), bool(), not and control tests in classes-source/1, including adopted descriptor binding and optional truth fallback. Length uses the explicit signed-64-bit contract; later protocols remain separate. |
Expand Down Expand Up @@ -151,7 +151,7 @@ issue families; availability alone never certifies an entire type or module.
| staticmethod | partial | staticmethod(value) and existing wrapper binding/direct-call rules; classes-source/1. | Unrestricted wrapper introspection. |
| str | partial | str() and str(x) for None/bool/int/str/adopted exceptions and user slots; existing/returned strings retain identity. Bytes/bytearray use finite binary representations. | Float/container/default-object displays, object= keyword and encoding forms. |
| sum | deferred | Not installed; resolving this builtin name raises Python NameError. | No implementation/API support credit from the negative availability probe. |
| super | deferred | Not installed; resolving this builtin name raises Python NameError. | No implementation/API support credit from the negative availability probe. |
| super | partial | Explicit bound/unbound and zero-argument super with lexical class cells, current C3 lookup, descriptors and proxy metadata in classes-source/1. | Builtin instance layouts, custom __class__ hooks and proxy reinitialization remain separate. |
| tuple | partial | Zero or one positional adopted iterable; primitives in both profiles, owned user iteration in classes-source/1 without user length hints. Existing tuple identity and shallow elements are preserved. | User length hints, methods, builtin subclassing and broader reflection remain separate stages. |
| type | partial | type(user_instance), one argument; classes-source/1. | Full one-argument and three-argument type construction. |
| vars | deferred | Not installed; resolving this builtin name raises Python NameError. | No implementation/API support credit from the negative availability probe. |
Expand Down
2 changes: 1 addition & 1 deletion docs/interpreter-construction.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
INT-OBJECT-01 adopts the following finite construction slices against CPython
3.14.7. `construction.json` records independent source/reference expectations;
an authored reference is not evidence that the independent engine implements it.
O3 is implemented; O4/O5 remain reference-only until their changes are accepted.
O3 and O4 are implemented; O5 remains reference-only until its changes are accepted.
Classes remain explicit in classes-source/1 and bytecode 5. The default
core-source/1 and bytecode 4 do not gain class syntax or reflective builtins.

Expand Down
3 changes: 2 additions & 1 deletion docs/interpreter-object-protocols.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ classes-source/1; core-source/1 keeps primitive arithmetic and generator support
| 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. |
| O3 | Allocation and initialization are adopted in the [construction contract](interpreter-construction.md), including __new__ and conditional initialization of its returned value. |
| Future | 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. |
| O4 | Explicit/zero-argument super and lexical class cells follow the [construction contract](interpreter-construction.md). |
| Future | 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 Down
3 changes: 2 additions & 1 deletion runtime/class-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const CLASS_SPECIAL_POLICY = Object.freeze(Object.fromEntries([
["__" + prefix + stem + "__", Object.freeze({...ordinarySlot, implicit: true})])),
...["__neg__", "__pos__", "__invert__"].map(name => [name, Object.freeze({...ordinarySlot, implicit: true})]),
["__init__", ordinarySlot], ["__new__", ordinarySlot], ...DESCRIPTOR_HOOKS.map(name => [name, ordinarySlot]),
["__module__", Object.freeze({define: true})], ["__qualname__", Object.freeze({define: true})]
["__module__", Object.freeze({define: true})], ["__qualname__", Object.freeze({define: true})],
["__class__", Object.freeze({define: true})]
]));
export const isSpecialName = name => typeof name === "string" && name.startsWith("__") && name.endsWith("__");
export function allowsClassSpecial(name, context) {
Expand Down
31 changes: 27 additions & 4 deletions runtime/class-values.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import {ClassValue, InstanceValue, BoundMethodValue, FunctionValue, BuiltinValue, EXCEPTIONS, PropertyValue, MethodWrapperValue, PropertyAccessorValue} from "./function-objects.mjs";
import {ClassValue, InstanceValue, BoundMethodValue, SuperValue, FunctionValue, BuiltinValue, EXCEPTIONS, PropertyValue, MethodWrapperValue, PropertyAccessorValue} from "./function-objects.mjs";
import {allowsClassSpecial, isSpecialName} from "./class-policy.mjs";
import {ValueRuntime, valueLayout} from "./values.mjs";
import {isPythonIdentifier} from "./source-identifiers.mjs";

export const CLASS_LIMITS = Object.freeze({bases: 16, mro: 64, attributes: 128, name: 256});

export class ClassRuntime {
constructor({memory = null, charge = () => {}, fail = null, values = null} = {}) {
constructor({memory = null, charge = () => {}, fail = null, values = null, builtin = null} = {}) {
this.memory = memory; this.charge = charge;
this.fail = fail || ((name, message, code = "PYTHON") => { throw Object.assign(new Error(message), {name, code}); });
this.values = values || new ValueRuntime({memory, charge, fail: this.fail});
this.builtin = builtin || (name => this.make(BuiltinValue, name));
}
make(Type, ...args) {
const value = new Type(...args), layout = valueLayout(value);
Expand Down Expand Up @@ -78,10 +79,10 @@ export class ClassRuntime {
// their original lookup/accounting path; only adopted overrides add phases.
return type.mro.some(base => !base.root && base.namespace.has(name));
}
lookup(type, name, native = true) {
lookup(type, name, native = true, start = 0) {
this.key(name); this.touch(type);
if (!(type instanceof ClassValue)) throw new TypeError("An adopted class is required.");
for (const base of type.mro) {
for (const base of type.mro.slice(start)) {
this.charge(1);
if (!native && base.root && ["__new__", "__init__"].includes(name)) continue;
if (base.namespace.has(name)) return {found: true, value: base.namespace.get(name)};
Expand Down Expand Up @@ -115,6 +116,16 @@ export class ClassRuntime {
read(receiver, name) {
this.key(name); this.touch(receiver);
const value = value => ({kind: "value", value});
if (receiver instanceof SuperValue) {
if (name === "__class__") return value(this.builtin("super"));
if (["__thisclass__", "__self__", "__self_class__"].includes(name))
return value(receiver[{__thisclass__: "thisclass", __self__: "self", __self_class__: "selfClass"}[name]]);
if (receiver.selfClass !== null) {
const item = this.lookup(receiver.selfClass, name, true, receiver.selfClass.mro.indexOf(receiver.thisclass) + 1);
if (item.found) return this.bindAttribute(item.value, receiver.self === receiver.selfClass ? null : receiver.self, receiver.selfClass);
}
this.fail("AttributeError", "super has no attribute '" + name + "'.");
}
if (receiver instanceof PropertyValue) {
if (["fget", "fset", "fdel"].includes(name)) return value(receiver[name]);
if (["getter", "setter", "deleter"].includes(name)) return value(this.make(PropertyAccessorValue, name, receiver));
Expand All @@ -134,6 +145,7 @@ export class ClassRuntime {
this.fail("UnsupportedOperationError", "Attribute receiver is outside the class profile.", "UNSUPPORTED");
if (receiver instanceof InstanceValue && name === "__class__") return value(receiver.type);
if (receiver instanceof ClassValue) {
if (name === "__class__") return value(this.builtin("type"));
if (["__name__", "__qualname__", "__module__"].includes(name)) return value(this.values.string(Array.from(
receiver[{__name__: "name", __qualname__: "qualname", __module__: "module"}[name]], char => char.codePointAt(0))));
if (["__bases__", "__mro__"].includes(name)) return value(this.values.tuple(name === "__bases__" ? receiver.bases : receiver.mro));
Expand All @@ -149,6 +161,8 @@ export class ClassRuntime {
return this.bindAttribute(item.value, receiver instanceof ClassValue ? null : receiver, owner);
}
bindAttribute(value, receiver, owner) {
if (value instanceof SuperValue && value.self === null && receiver !== null)
return {kind: "value", value: this.superProxy(value.thisclass, receiver)};
if (receiver !== null && value instanceof PropertyValue) {
if (value.fget === null) this.fail("AttributeError", "Property has no getter.");
return {kind: "call", callable: value.fget, args: [receiver]};
Expand Down Expand Up @@ -176,6 +190,7 @@ export class ClassRuntime {
}
write(receiver, name, value, remove = false) {
this.key(name); this.touch(receiver);
if (receiver instanceof SuperValue) this.fail("AttributeError", "super attributes are read-only");
if (receiver instanceof InstanceValue) {
const item = this.lookup(receiver.type, name), descriptor = item.found ? this.descriptor(item.value) : null;
if (item.value instanceof PropertyValue) {
Expand Down Expand Up @@ -212,6 +227,14 @@ export class ClassRuntime {
for (const base of type.mro) { this.charge(1); if (base === parent) return true; }
return false;
}
superProxy(type, receiver = null) {
if (!(type instanceof ClassValue)) this.fail("TypeError", "super requires an adopted type");
this.touch(type);
if (receiver === null) return this.make(SuperValue, type, null, null);
const owner = receiver instanceof ClassValue ? receiver : receiver instanceof InstanceValue ? receiver.type : null;
if (owner === null || !this.subclass(owner, type)) this.fail("TypeError", "super receiver must be an instance or subtype of its first argument");
return this.make(SuperValue, type, receiver, owner);
}
callable(value) {
this.touch(value);
if (value instanceof InstanceValue) return this.lookup(value.type, "__call__").found;
Expand Down
3 changes: 1 addition & 2 deletions runtime/classes-source.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,8 @@ test('class imports share object identity while inherited methods retain their d
assert.equal(result.closed.bytes,0);assert.equal(result.closed.roots,0);
});

test('metadata overrides and implicit class cells refuse before source effects',async()=>{
test('metadata overrides and later class stages refuse before source effects',async()=>{
for(const source of [
'class A:\n def f(self): return __class__\n',
'class A:\n __module__="custom"\n',
'class A:\n def __qualname__(self): pass\n',
'class A:\n from helper import value as __module__\n',
Expand Down
Loading
Loading