Skip to content

Commit c0b04e3

Browse files
committed
Generic work
1 parent 536cf13 commit c0b04e3

File tree

7 files changed

+224
-0
lines changed

7 files changed

+224
-0
lines changed

Include/internal/pycore_intrinsics.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define INTRINSIC_TYPEVAR 7
1111
#define INTRINSIC_PARAMSPEC 8
1212
#define INTRINSIC_TYPEVARTUPLE 9
13+
#define INTRINSIC_SUBSCRIPT_GENERIC 10
1314

1415
#define MAX_INTRINSIC_1 9
1516

Include/internal/pycore_typevarobject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ extern PyTypeObject _PyTypeVarTuple_Type;
1313
extern PyTypeObject _PyParamSpec_Type;
1414
extern PyTypeObject _PyParamSpecArgs_Type;
1515
extern PyTypeObject _PyParamSpecKwargs_Type;
16+
extern PyTypeObject _PyGeneric_Type;
1617

1718
extern PyObject *_Py_make_typevar(const char *, PyObject *);
1819
extern PyObject *_Py_make_paramspec(const char *);
1920
extern PyObject *_Py_make_typevartuple(const char *);
21+
extern PyObject *_Py_subscript_generic(PyObject *);
2022

2123
#ifdef __cplusplus
2224
}

Lib/typing.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,93 @@ def __init_subclass__(cls, *args, **kwargs):
19691969
cls.__parameters__ = tuple(tvars)
19701970

19711971

1972+
@_tp_cache
1973+
def _generic_class_getitem(cls, params):
1974+
"""Parameterizes a generic class.
1975+
1976+
At least, parameterizing a generic class is the *main* thing this method
1977+
does. For example, for some generic class `Foo`, this is called when we
1978+
do `Foo[int]` - there, with `cls=Foo` and `params=int`.
1979+
1980+
However, note that this method is also called when defining generic
1981+
classes in the first place with `class Foo(Generic[T]): ...`.
1982+
"""
1983+
if not isinstance(params, tuple):
1984+
params = (params,)
1985+
1986+
params = tuple(_type_convert(p) for p in params)
1987+
if cls in (builtins.Generic, Protocol):
1988+
# Generic and Protocol can only be subscripted with unique type variables.
1989+
if not params:
1990+
raise TypeError(
1991+
f"Parameter list to {cls.__qualname__}[...] cannot be empty"
1992+
)
1993+
if not all(_is_typevar_like(p) for p in params):
1994+
raise TypeError(
1995+
f"Parameters to {cls.__name__}[...] must all be type variables "
1996+
f"or parameter specification variables.")
1997+
if len(set(params)) != len(params):
1998+
raise TypeError(
1999+
f"Parameters to {cls.__name__}[...] must all be unique")
2000+
else:
2001+
# Subscripting a regular Generic subclass.
2002+
for param in cls.__parameters__:
2003+
prepare = getattr(param, '__typing_prepare_subst__', None)
2004+
if prepare is not None:
2005+
params = prepare(cls, params)
2006+
_check_generic(cls, params, len(cls.__parameters__))
2007+
2008+
new_args = []
2009+
for param, new_arg in zip(cls.__parameters__, params):
2010+
if isinstance(param, TypeVarTuple):
2011+
new_args.extend(new_arg)
2012+
else:
2013+
new_args.append(new_arg)
2014+
params = tuple(new_args)
2015+
2016+
return _GenericAlias(cls, params,
2017+
_paramspec_tvars=True)
2018+
2019+
import builtins
2020+
2021+
def _generic_init_subclass(cls, *args, **kwargs):
2022+
super(builtins.Generic, cls).__init_subclass__(*args, **kwargs)
2023+
tvars = []
2024+
if '__orig_bases__' in cls.__dict__:
2025+
error = builtins.Generic in cls.__orig_bases__
2026+
else:
2027+
error = (builtins.Generic in cls.__bases__ and
2028+
cls.__name__ != 'Protocol' and
2029+
type(cls) != _TypedDictMeta)
2030+
if error:
2031+
raise TypeError("Cannot inherit from plain Generic")
2032+
if '__orig_bases__' in cls.__dict__:
2033+
tvars = _collect_parameters(cls.__orig_bases__)
2034+
# Look for Generic[T1, ..., Tn].
2035+
# If found, tvars must be a subset of it.
2036+
# If not found, tvars is it.
2037+
# Also check for and reject plain Generic,
2038+
# and reject multiple Generic[...].
2039+
gvars = None
2040+
for base in cls.__orig_bases__:
2041+
if (isinstance(base, _GenericAlias) and
2042+
base.__origin__ is builtins.Generic):
2043+
if gvars is not None:
2044+
raise TypeError(
2045+
"Cannot inherit from Generic[...] multiple types.")
2046+
gvars = base.__parameters__
2047+
if gvars is not None:
2048+
tvarset = set(tvars)
2049+
gvarset = set(gvars)
2050+
if not tvarset <= gvarset:
2051+
s_vars = ', '.join(str(t) for t in tvars if t not in gvarset)
2052+
s_args = ', '.join(str(g) for g in gvars)
2053+
raise TypeError(f"Some type variables ({s_vars}) are"
2054+
f" not listed in Generic[{s_args}]")
2055+
tvars = gvars
2056+
cls.__parameters__ = tuple(tvars)
2057+
2058+
19722059
class _TypingEllipsis:
19732060
"""Internal placeholder for ... (ellipsis)."""
19742061

Objects/typevarobject.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class paramspec "paramspecobject *" "&_PyParamSpec_Type"
1111
class paramspecargs "paramspecargsobject *" "&_PyParamSpecArgs_Type"
1212
class paramspeckwargs "paramspeckwargsobject *" "&_PyParamSpecKwargs_Type"
1313
class typevartuple "typevartupleobject *" "&_PyTypeVarTuple_Type"
14+
class Generic "PyObject *" "&PyGeneric_Type
1415
[clinic start generated code]*/
1516
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=74cb9c15a049111b]*/
1617

@@ -821,3 +822,111 @@ PyObject *_Py_make_paramspec(const char *name) {
821822
PyObject *_Py_make_typevartuple(const char *name) {
822823
return (PyObject *)typevartupleobject_alloc(name);
823824
}
825+
826+
PyDoc_STRVAR(generic_doc,
827+
"Abstract base class for generic types.\n\
828+
\n\
829+
A generic type is typically declared by inheriting from\n\
830+
this class parameterized with one or more type variables.\n\
831+
For example, a generic mapping type might be defined as::\n\
832+
\n\
833+
class Mapping(Generic[KT, VT]):\n\
834+
def __getitem__(self, key: KT) -> VT:\n\
835+
...\n\
836+
# Etc.\n\
837+
\n\
838+
This class can then be used as follows::\n\
839+
\n\
840+
def lookup_name(mapping: Mapping[KT, VT], key: KT, default: VT) -> VT:\n\
841+
try:\n\
842+
return mapping[key]\n\
843+
except KeyError:\n\
844+
return default\n\
845+
");
846+
847+
PyDoc_STRVAR(generic_class_getitem_doc,
848+
"Parameterizes a generic class.\n\
849+
\n\
850+
At least, parameterizing a generic class is the *main* thing this method\n\
851+
does. For example, for some generic class `Foo`, this is called when we\n\
852+
do `Foo[int]` - there, with `cls=Foo` and `params=int`.\n\
853+
\n\
854+
However, note that this method is also called when defining generic\n\
855+
classes in the first place with `class Foo(Generic[T]): ...`.\n\
856+
");
857+
858+
static PyObject *
859+
call_typing_args_kwargs(const char *name, PyTypeObject *cls, PyObject *args, PyObject *kwargs)
860+
{
861+
PyObject *typing = NULL, *func = NULL, *new_args = NULL;
862+
typing = PyImport_ImportModule("typing");
863+
if (typing == NULL) {
864+
goto error;
865+
}
866+
func = PyObject_GetAttrString(typing, name);
867+
if (func == NULL) {
868+
goto error;
869+
}
870+
assert(PyTuple_Check(args));
871+
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
872+
new_args = PyTuple_New(nargs + 1);
873+
if (new_args == NULL) {
874+
goto error;
875+
}
876+
PyTuple_SET_ITEM(new_args, 0, Py_NewRef((PyObject *)cls));
877+
for (Py_ssize_t i = 0; i < nargs; i++) {
878+
PyObject *arg = PyTuple_GET_ITEM(args, i);
879+
PyTuple_SET_ITEM(new_args, i + 1, Py_NewRef(arg));
880+
}
881+
PyObject *result = PyObject_Call(func, new_args, kwargs);
882+
Py_DECREF(func);
883+
Py_DECREF(typing);
884+
Py_DecRef(new_args);
885+
return result;
886+
error:
887+
Py_XDECREF(typing);
888+
Py_XDECREF(func);
889+
Py_XDECREF(new_args);
890+
return NULL;
891+
}
892+
893+
static PyObject *
894+
generic_init_subclass(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
895+
{
896+
return call_typing_args_kwargs("_generic_init_subclass", cls, args, kwargs);
897+
}
898+
899+
static PyObject *
900+
generic_class_getitem(PyTypeObject *cls, PyObject *args, PyObject *kwargs)
901+
{
902+
return call_typing_args_kwargs("_generic_class_getitem", cls, args, kwargs);
903+
}
904+
905+
PyObject *
906+
_Py_subscript_generic(PyObject *params)
907+
{
908+
PyObject *args = PyTuple_Pack(2, &_PyGeneric_Type, params);
909+
if (args == NULL) {
910+
return NULL;
911+
}
912+
return call_typing_func_object("_generic_class_getitem", args);
913+
}
914+
915+
static PyMethodDef generic_methods[] = {
916+
{"__class_getitem__", (PyCFunction)(void (*)(void))generic_class_getitem,
917+
METH_VARARGS | METH_KEYWORDS | METH_CLASS,
918+
generic_class_getitem_doc},
919+
{"__init_subclass__", (PyCFunction)(void (*)(void))generic_init_subclass,
920+
METH_VARARGS | METH_KEYWORDS | METH_CLASS,
921+
PyDoc_STR("Function to initialize subclasses.")},
922+
{NULL} /* Sentinel */
923+
};
924+
925+
PyTypeObject _PyGeneric_Type = {
926+
PyVarObject_HEAD_INIT(&PyType_Type, 0)
927+
.tp_name = "typing.Generic",
928+
.tp_basicsize = sizeof(PyObject),
929+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
930+
.tp_doc = generic_doc,
931+
.tp_methods = generic_methods,
932+
};

Python/bltinmodule.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3096,6 +3096,7 @@ _PyBuiltin_Init(PyInterpreterState *interp)
30963096
SETBUILTIN("TypeVar", &_PyTypeVar_Type);
30973097
SETBUILTIN("TypeVarTuple", &_PyTypeVarTuple_Type);
30983098
SETBUILTIN("ParamSpec", &_PyParamSpec_Type);
3099+
SETBUILTIN("Generic", &_PyGeneric_Type);
30993100
debug = PyBool_FromLong(config->optimization_level == 0);
31003101
if (PyDict_SetItemString(dict, "__debug__", debug) < 0) {
31013102
Py_DECREF(debug);

Python/intrinsics.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ make_typevartuple(PyThreadState* unused, PyObject *v)
221221
return _Py_make_typevartuple(PyUnicode_AsUTF8(v));
222222
}
223223

224+
static PyObject *
225+
subscript_generic(PyThreadState* unused, PyObject *v)
226+
{
227+
return _Py_subscript_generic(v);
228+
}
229+
224230
const instrinsic_func1
225231
_PyIntrinsics_UnaryFunctions[] = {
226232
[0] = no_intrinsic,
@@ -233,6 +239,7 @@ _PyIntrinsics_UnaryFunctions[] = {
233239
[INTRINSIC_TYPEVAR] = make_typevar,
234240
[INTRINSIC_PARAMSPEC] = make_paramspec,
235241
[INTRINSIC_TYPEVARTUPLE] = make_typevartuple,
242+
[INTRINSIC_SUBSCRIPT_GENERIC] = subscript_generic,
236243
};
237244

238245

Python/symtable.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,6 +1331,23 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
13311331
DEF_LOCAL, LOCATION(s))) {
13321332
VISIT_QUIT(st, 0);
13331333
}
1334+
for (Py_ssize_t i = 0; i < asdl_seq_LEN(s->v.ClassDef.typeparams); i++) {
1335+
typeparam_ty tp = asdl_seq_GET(s->v.ClassDef.typeparams, i);
1336+
PyObject *name;
1337+
switch (tp->kind) {
1338+
case TypeVar_kind:
1339+
name = tp->v.TypeVar.name;
1340+
break;
1341+
case ParamSpec_kind:
1342+
name = tp->v.ParamSpec.name;
1343+
break;
1344+
case TypeVarTuple_kind:
1345+
name = tp->v.TypeVarTuple.name;
1346+
break;
1347+
}
1348+
if (!symtable_add_def(st, name, USE, LOCATION(s)))
1349+
VISIT_QUIT(st, 0);
1350+
}
13341351
}
13351352
VISIT_SEQ(st, stmt, s->v.ClassDef.body);
13361353
st->st_private = tmp;

0 commit comments

Comments
 (0)