diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 2005dd9b0866bd..bda26bc7464c5a 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -2263,6 +2263,44 @@ def testfunc(n): self.assertNotIn("_GUARD_TOS_UNICODE", uops) self.assertIn("_BINARY_OP_ADD_UNICODE", uops) + def test_format_simple_narrows_to_str(self): + def testfunc(n): + x = [] + for _ in range(n): + v = 42 + s = f"{v}" + t = "hello" + s + x.append(t) + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, ["hello42"] * TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + + self.assertIn("_FORMAT_SIMPLE", uops) + self.assertNotIn("_GUARD_TOS_UNICODE", uops) + self.assertIn("_BINARY_OP_ADD_UNICODE", uops) + + def test_format_with_spec_narrows_to_str(self): + def testfunc(n): + x = [] + for _ in range(n): + v = 3.14 + s = f"{v:.2f}" + t = "pi=" + s + x.append(t) + return x + + res, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertEqual(res, ["pi=3.14"] * TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + + self.assertIn("_FORMAT_WITH_SPEC", uops) + self.assertNotIn("_GUARD_TOS_UNICODE", uops) + self.assertIn("_BINARY_OP_ADD_UNICODE", uops) + def test_binary_op_subscr_str_int(self): def testfunc(n): x = 0 diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-17-01-34.gh-issue-131798.WSefcr.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-17-01-34.gh-issue-131798.WSefcr.rst new file mode 100644 index 00000000000000..3f7e7fa0c37a5b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-03-30-17-01-34.gh-issue-131798.WSefcr.rst @@ -0,0 +1,2 @@ +Allow the JIT to remove unicode guards after ``_FORMAT_SIMPLE`` and +``_FORMAT_WITH_SPEC`` by setting the return type to string. diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 792f83cdbd2d3a..814f4a632c5fcc 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -1551,6 +1551,14 @@ dummy_func(void) { set = sym_new_type(ctx, &PySet_Type); } + op(_FORMAT_SIMPLE, (value -- res)) { + res = sym_new_type(ctx, &PyUnicode_Type); + } + + op(_FORMAT_WITH_SPEC, (value, fmt_spec -- res)) { + res = sym_new_type(ctx, &PyUnicode_Type); + } + op(_SET_UPDATE, (set, unused[oparg-1], iterable -- set, unused[oparg-1], i)) { (void)set; i = iterable; diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 7539133fb92096..9be588726f2760 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -4280,14 +4280,14 @@ case _FORMAT_SIMPLE: { JitOptRef res; - res = sym_new_not_null(ctx); + res = sym_new_type(ctx, &PyUnicode_Type); stack_pointer[-1] = res; break; } case _FORMAT_WITH_SPEC: { JitOptRef res; - res = sym_new_not_null(ctx); + res = sym_new_type(ctx, &PyUnicode_Type); CHECK_STACK_BOUNDS(-1); stack_pointer[-2] = res; stack_pointer += -1;