diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 501f482570ac32..1e50ed57c541b6 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -329,6 +329,29 @@ async def f_set(): with self.assertRaises(TypeError): run_yielding_async_fn(f) + def test_frozenset_optimization_shared_set(self): + # gh-158490: frozenset({... for ...}) must not freeze a set that + # something else still references, here a trace function + kept = [] + def tracer(frame, event, arg): + if event == 'return' and type(arg) is set: + kept.append(arg) + return tracer + + class C: + x: frozenset({i for i in range(3)}) + + old_trace = sys.gettrace() + sys.settrace(tracer) + try: + fs = C.__annotations__['x'] + finally: + sys.settrace(old_trace) + self.assertEqual(fs, frozenset({0, 1, 2})) + self.assertEqual(len(kept), 1) + self.assertIs(type(kept[0]), set) + self.assertIsNot(kept[0], fs) + def test_ascii(self): self.assertEqual(ascii(''), '\'\'') self.assertEqual(ascii(0), '0') diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-03-17-53-21.gh-issue-158490.U63vs5.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-03-17-53-21.gh-issue-158490.U63vs5.rst new file mode 100644 index 00000000000000..80d28b10bf11ae --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-03-17-53-21.gh-issue-158490.U63vs5.rst @@ -0,0 +1,2 @@ +Fix :class:`frozenset` of a set comprehension in a class annotation or type +alias freezing a set that a trace function or debugger still references. diff --git a/Python/codegen.c b/Python/codegen.c index a975705576a167..f7b7be290f9957 100644 --- a/Python/codegen.c +++ b/Python/codegen.c @@ -4029,6 +4029,20 @@ maybe_optimize_function_call(compiler *c, expr_ty e, jump_target_label end) if (_PyUnicode_EqualToASCIIString(func->v.Name.id, "frozenset") && (arg_expr->kind == Set_kind || arg_expr->kind == SetComp_kind)) { + if (arg_expr->kind == SetComp_kind) { + /* A comprehension that is not inlined returns its set from a + call, so a trace function may still hold a reference to it. */ + PySTEntryObject *entry = _PySymtable_Lookup(SYMTABLE(c), + (void *)arg_expr); + if (entry == NULL) { + return ERROR; + } + int is_inlined = (entry->ste_type == InlinedComprehensionBlock); + Py_DECREF(entry); + if (!is_inlined) { + return 0; + } + } NEW_JUMP_TARGET_LABEL(c, skip_optimization); ADDOP_I(c, loc, COPY, 1);