Skip to content

gh-119395: Fix leaky mangling after generic classes #119399

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
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
3 changes: 2 additions & 1 deletion Lib/importlib/_bootstrap_external.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ def _write_atomic(path, data, mode=0o666):
# Python 3.13a5 3569 (Specialize CONTAINS_OP)
# Python 3.13a6 3570 (Add __firstlineno__ class attribute)
# Python 3.14a1 3600 (Add LOAD_COMMON_CONSTANT)
# Python 3.14a1 3601 (Fix leaky name mangling in generic classes)

# Python 3.15 will start with 3700

Expand All @@ -489,7 +490,7 @@ def _write_atomic(path, data, mode=0o666):
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.

MAGIC_NUMBER = (3571).to_bytes(2, 'little') + b'\r\n'
MAGIC_NUMBER = (3601).to_bytes(2, 'little') + b'\r\n'

_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c

Expand Down
33 changes: 33 additions & 0 deletions Lib/test/test_type_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,39 @@ def meth[__U](self, arg: __T, arg2: __U):

self.assertEqual(Foo.Alias.__value__, (T, V))

def test_no_leaky_mangling_in_module(self):
ns = run_code("""
__before = "before"
class X[T]: pass
__after = "after"
""")
self.assertEqual(ns["__before"], "before")
self.assertEqual(ns["__after"], "after")

def test_no_leaky_mangling_in_function(self):
ns = run_code("""
def f():
class X[T]: pass
_X_foo = 2
__foo = 1
assert locals()['__foo'] == 1
return __foo
""")
self.assertEqual(ns["f"](), 1)

def test_no_leaky_mangling_in_class(self):
ns = run_code("""
class Outer:
__before = "before"
class Inner[T]:
__x = "inner"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a good idea to test __before = "class"; __after = "class" names in the class scope here? Or do we have them tested?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean in the inner class? That wouldn't really be relevant here since there is nothing else interesting going on in the inner class body.

__after = "after"
""")
Outer = ns["Outer"]
self.assertEqual(Outer._Outer__before, "before")
self.assertEqual(Outer.Inner._Inner__x, "inner")
self.assertEqual(Outer._Outer__after, "after")


class TypeParamsComplexCallsTest(unittest.TestCase):
def test_defaults(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix bug where any names appearing after a generic class were mangled as if
they were part of the generic class.
2 changes: 1 addition & 1 deletion Python/compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2625,7 +2625,6 @@ compiler_class(struct compiler *c, stmt_ty s)
asdl_type_param_seq *type_params = s->v.ClassDef.type_params;
int is_generic = asdl_seq_LEN(type_params) > 0;
if (is_generic) {
Py_XSETREF(c->u->u_private, Py_NewRef(s->v.ClassDef.name));
PyObject *type_params_name = PyUnicode_FromFormat("<generic parameters of %U>",
s->v.ClassDef.name);
if (!type_params_name) {
Expand All @@ -2636,6 +2635,7 @@ compiler_class(struct compiler *c, stmt_ty s)
Py_DECREF(type_params_name);
return ERROR;
}
Py_XSETREF(c->u->u_private, Py_NewRef(s->v.ClassDef.name));
Py_DECREF(type_params_name);
RETURN_IF_ERROR_IN_SCOPE(c, compiler_type_params(c, type_params));
_Py_DECLARE_STR(type_params, ".type_params");
Expand Down
7 changes: 3 additions & 4 deletions Python/symtable.c
Original file line number Diff line number Diff line change
Expand Up @@ -1508,7 +1508,6 @@ symtable_enter_type_param_block(struct symtable *st, identifier name,
lineno, col_offset, end_lineno, end_col_offset)) {
return 0;
}
st->st_private = name;
// This is used for setting the generic base
_Py_DECLARE_STR(generic_base, ".generic_base");
if (!symtable_add_def(st, &_Py_STR(generic_base), DEF_LOCAL,
Expand Down Expand Up @@ -1668,18 +1667,19 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
}
break;
case ClassDef_kind: {
PyObject *tmp;
if (!symtable_add_def(st, s->v.ClassDef.name, DEF_LOCAL, LOCATION(s)))
VISIT_QUIT(st, 0);
if (s->v.ClassDef.decorator_list)
VISIT_SEQ(st, expr, s->v.ClassDef.decorator_list);
PyObject *tmp = st->st_private;
if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) {
if (!symtable_enter_type_param_block(st, s->v.ClassDef.name,
(void *)s->v.ClassDef.type_params,
false, false, s->kind,
LOCATION(s))) {
VISIT_QUIT(st, 0);
}
st->st_private = s->v.ClassDef.name;
VISIT_SEQ(st, type_param, s->v.ClassDef.type_params);
}
VISIT_SEQ(st, expr, s->v.ClassDef.bases);
Expand All @@ -1688,7 +1688,6 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
(void *)s, s->lineno, s->col_offset,
s->end_lineno, s->end_col_offset))
VISIT_QUIT(st, 0);
tmp = st->st_private;
st->st_private = s->v.ClassDef.name;
if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) {
if (!symtable_add_def(st, &_Py_ID(__type_params__),
Expand All @@ -1702,13 +1701,13 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
}
}
VISIT_SEQ(st, stmt, s->v.ClassDef.body);
st->st_private = tmp;
if (!symtable_exit_block(st))
VISIT_QUIT(st, 0);
if (asdl_seq_LEN(s->v.ClassDef.type_params) > 0) {
if (!symtable_exit_block(st))
VISIT_QUIT(st, 0);
}
st->st_private = tmp;
break;
}
case TypeAlias_kind: {
Expand Down
Loading