Skip to main content

1672. gopy type

What we are porting

PyTypeObject is the meta-class. Every Python value has an ob_type pointing to its PyTypeObject. The type carries slots: function pointers for arithmetic, comparison, hashing, calling, attribute access, and so on. Slots are the dispatch table that drives the entire object protocol.

CPython has roughly 80 slots split across tp_* (type-level), nb_* (numeric protocol, PyNumberMethods), sq_* (sequence, PySequenceMethods), mp_* (mapping, PyMappingMethods), am_* (async, PyAsyncMethods), bf_* (buffer).

v0.2 ships only the slots needed by the gate plus the fields used by every type. The rest land slot-by-slot as later phases need them.

v0.2 slot table

type Type struct {
Header

Name string // tp_name
BaseSize int // tp_basicsize, used by GC sizing later
ItemSize int // tp_itemsize, for VarObject types

Bases []*Type // tp_bases
MRO []*Type // tp_mro

Repr func(o Object) (string, error) // tp_repr
Str func(o Object) (string, error) // tp_str
Hash func(o Object) (int64, error) // tp_hash
RichCmp func(a, b Object, op CompareOp) (Object, error) // tp_richcompare
Iter func(o Object) (Object, error) // tp_iter
IterNext func(o Object) (Object, error) // tp_iternext
Call func(o Object, args []Object, kwargs map[string]Object) (Object, error) // tp_call
Dealloc func(o Object) // tp_dealloc

Number *NumberMethods // tp_as_number
Sequence *SequenceMethods // tp_as_sequence
Mapping *MappingMethods // tp_as_mapping
}

Slots are nil when the type does not implement them. CPython uses NULL the same way.

The remaining slots (getattro, setattro, descr_get, descr_set, init, new, alloc, free, traverse, clear, finalize, weaklist, dict, mro_entries, init_subclass, set_name, vectorcall) arrive in later phases:

  • init, new, alloc: v0.7 once we have a parser.
  • getattro, setattro, descr_*: v0.2 covers attribute access only as needed by the gate. Full descriptor protocol is v0.4.
  • traverse, clear, finalize, weaklist: v0.10 (cycle GC).
  • vectorcall: v0.6 (VM Tier-1 fast call path).

CompareOp

type CompareOp int

const (
CompareLT CompareOp = iota
CompareLE
CompareEQ
CompareNE
CompareGT
CompareGE
)

Same numeric values as CPython's Py_LT/Py_LE/.../Py_GE (0..5).

NumberMethods, SequenceMethods, MappingMethods

These are smaller slot bundles. v0.2 needs a subset:

type NumberMethods struct {
Add func(a, b Object) (Object, error)
Subtract func(a, b Object) (Object, error)
Multiply func(a, b Object) (Object, error)
Negative func(o Object) (Object, error)
Bool func(o Object) (bool, error)
Int func(o Object) (Object, error)
Float func(o Object) (Object, error)
}

type SequenceMethods struct {
Length func(o Object) (int, error)
Concat func(a, b Object) (Object, error)
Repeat func(o Object, n int) (Object, error)
GetItem func(o Object, i int) (Object, error)
SetItem func(o Object, i int, v Object) error
Contains func(o, v Object) (bool, error)
}

type MappingMethods struct {
Length func(o Object) (int, error)
GetItem func(o, key Object) (Object, error)
SetItem func(o, key, v Object) error
DelItem func(o, key Object) error
}

Add/Subtract/Multiply lack reflected variants here because the abstract layer (abstract/numeric.go) handles fallback to the right operand's type. CPython does the same in BINARY_OP1.

MRO

tp_mro is the C3 linearization of tp_bases. v0.2 lands C3 in typeobj/mro.go ported from Objects/typeobject.c:mro_implementation.

For built-in types in v0.2 the MRO is trivial: each type's bases is [object], except bool which is [int] and object which has no base. C3 still runs to keep the path identical.

type.mro(int) == [int, object]
type.mro(bool) == [bool, int, object]
type.mro(tuple) == [tuple, object]

Slot lookup

lookup_maybe_method walks the MRO. v0.2 has no Python-defined types yet, so this is a degenerate walk: just check tp_<slot> on the type itself, fall through to bases. We still ship the walk because v0.3 exceptions and v0.5 user types depend on it.

func (t *Type) Lookup(slot string) Object { ... }

What "subtype" means in v0.2

Pure read-only subtype checks (isinstance(x, T)) need a base-walk only. v0.2 supports those. Subtype creation (type(name, bases, dict)) requires tp_new/tp_init/tp_alloc which arrive in v0.7.

File mapping

C sourceGo target
Include/cpython/typeobject.hobjects/type.go (struct)
Objects/typeobject.c:type_reprtypeobj/repr.go
Objects/typeobject.c:type_calltypeobj/call.go
Objects/typeobject.c:mro_*typeobj/mro.go
Objects/typeobject.c:lookup_maybe_*typeobj/lookup.go
Objects/typeobject.c:type_getattrotypeobj/getattr.go (v0.4)
Objects/typeobject.c:type_newtypeobj/new.go (v0.7)

Checklist

Status legend: [x] shipped, [ ] pending, [~] partial / scaffold, [n] deferred / not in scope this phase.

Files

  • [~] objects/type.go: Type struct with the v0.2 slot subset. Landed for the v0.2 gate; Number / Sequence / Mapping bundles still partially typed.
  • [~] objects/compareop.go: CompareOp constants (CompareLT..GE) pinned to CPython's 0..5.
  • objects/numbermethods.go: full NumberMethods matching PyNumberMethods field-for-field, not just the v0.2 subset.
  • objects/sequencemethods.go: full SequenceMethods.
  • objects/mappingmethods.go: full MappingMethods.
  • [~] typeobj/mro.go: C3 linearisation. Trivial-base path landed; multi-base panel pending the v0.7 user-type port.
  • [~] typeobj/lookup.go: Lookup(slot) MRO walk. v0.2 degenerate path landed; the descriptor-aware walk lands in v0.4.
  • typeobj/repr.go: type_repr (<class 'foo'> formatting).
  • typeobj/call.go: type_call (instance creation via tp_new + tp_init).
  • typeobj/getattr.go: type_getattro with the data-descriptor beats instance dict beats non-data-descriptor ordering.
  • typeobj/new.go: type_new, type.__init__, the metaclass resolution rule.
  • typeobj/inherit.go: slot inheritance + inherit_slots fixpoint that CPython runs at type-creation time.

Surface guarantees

  • CompareOp numeric values match Py_LT..Py_GE (0..5).
  • type.__mro__ matches CPython's C3 output for every multi-base pattern in Lib/test/test_descr.py. Pinned by compat/mro_test.go.
  • Lookup returns the same descriptor that _PyType_Lookup returns, including for slot wrappers that live on object.
  • type_repr produces <class 'name'> exactly, including module prefix for non-builtin types.
  • Slot inheritance fixpoint converges in the same iteration count as CPython for the builtin hierarchy. Diagnostic only; not user-observable.

Out of scope for v0.2

  • tp_new, tp_init, tp_alloc, tp_free. Land in v0.7 alongside user-defined types.
  • tp_traverse, tp_clear, tp_finalize, tp_weaklistoffset, tp_dictoffset. Land in v0.10 with cycle GC and weakrefs.
  • tp_vectorcall_offset, tp_vectorcall. Land in v0.6 with the VM Tier-1 fast call path.
  • tp_descr_get, tp_descr_set. Land in v0.4 with the descriptor protocol port.
  • Metaclass resolution beyond type itself. Lands in v0.7.