Skip to content

Commit

Permalink
pythongh-104600: Make function.__type_params__ writable
Browse files Browse the repository at this point in the history
  • Loading branch information
JelleZijlstra committed May 18, 2023
1 parent c5b670e commit 7c04199
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 3 deletions.
2 changes: 1 addition & 1 deletion Lib/functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
# wrapper functions that can handle naive introspection

WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
'__annotations__', '__type_params__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
Expand Down
15 changes: 15 additions & 0 deletions Lib/test/test_funcattrs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import textwrap
import types
import typing
import unittest


Expand Down Expand Up @@ -190,6 +191,20 @@ def test___qualname__(self):
# __qualname__ must be a string
self.cannot_set_attr(self.b, '__qualname__', 7, TypeError)

def test___type_params__(self):
def generic[T](): pass
def not_generic(): pass
T, = generic.__type_params__
self.assertIsInstance(T, typing.TypeVar)
self.assertEqual(generic.__type_params__, (T,))
self.assertEqual(not_generic.__type_params__, ())
with self.assertRaises(TypeError):
del not_generic.__type_params__
with self.assertRaises(TypeError):
not_generic.__type_params__ = 42
not_generic.__type_params__ = (T,)
self.assertEqual(not_generic.__type_params__, (T,))

def test___code__(self):
num_one, num_two = 7, 8
def a(): pass
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_functools.py
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ def check_wrapper(self, wrapper, wrapped,


def _default_update(self):
def f(a:'This is a new annotation'):
def f[T](a:'This is a new annotation'):
"""This is a test"""
pass
f.attr = 'This is also a test'
Expand All @@ -630,12 +630,14 @@ def wrapper(b:'This is the prior annotation'):
def test_default_update(self):
wrapper, f = self._default_update()
self.check_wrapper(wrapper, f)
T, = f.__type_params__
self.assertIs(wrapper.__wrapped__, f)
self.assertEqual(wrapper.__name__, 'f')
self.assertEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.attr, 'This is also a test')
self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation')
self.assertNotIn('b', wrapper.__annotations__)
self.assertEqual(wrapper.__type_params__, (T,))

@unittest.skipIf(sys.flags.optimize >= 2,
"Docstrings are omitted with -O2 and above")
Expand Down
17 changes: 16 additions & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,20 @@ func_get_type_params(PyFunctionObject *op, void *Py_UNUSED(ignored))
return Py_NewRef(op->func_typeparams);
}

static int
func_set_type_params(PyFunctionObject *op, PyObject *value, void *Py_UNUSED(ignored))
{
/* Not legal to del f.__type_params__ or to set it to anything
* other than a tuple object. */
if (value == NULL || !PyTuple_Check(value)) {
PyErr_SetString(PyExc_TypeError,
"__type_params__ must be set to a tuple");
return -1;
}
Py_XSETREF(op->func_typeparams, Py_NewRef(value));
return 0;
}

PyObject *
_Py_set_function_type_params(PyThreadState *Py_UNUSED(ignored), PyObject *func,
PyObject *type_params)
Expand All @@ -687,7 +701,8 @@ static PyGetSetDef func_getsetlist[] = {
{"__dict__", PyObject_GenericGetDict, PyObject_GenericSetDict},
{"__name__", (getter)func_get_name, (setter)func_set_name},
{"__qualname__", (getter)func_get_qualname, (setter)func_set_qualname},
{"__type_params__", (getter)func_get_type_params, NULL},
{"__type_params__", (getter)func_get_type_params,
(setter)func_set_type_params},
{NULL} /* Sentinel */
};

Expand Down

0 comments on commit 7c04199

Please sign in to comment.