From 48d6ad4fb2622814fc641a23eebf8b5a119f9327 Mon Sep 17 00:00:00 2001 From: Eddy Xu Date: Wed, 18 Mar 2026 00:00:08 -0400 Subject: [PATCH 1/3] Add warning for 5 features. --- Lib/contextlib.py | 4 +-- Lib/test/test_py3kwarn.py | 35 +++++++++++++++++++++-- Objects/dictobject.c | 59 +++++++++++++++++++++++++++++++++++++++ Objects/rangeobject.c | 57 +++++++++++++++++++++++++++++++++++-- Objects/typeobject.c | 4 +++ Python/bltinmodule.c | 53 +++++++++++++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 7 deletions(-) diff --git a/Lib/contextlib.py b/Lib/contextlib.py index f05205b01c2f7c..7cac00244d0f6e 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -14,14 +14,14 @@ def __init__(self, gen): def __enter__(self): try: - return self.gen.next() + return next(self.gen) except StopIteration: raise RuntimeError("generator didn't yield") def __exit__(self, type, value, traceback): if type is None: try: - self.gen.next() + next(self.gen) except StopIteration: return else: diff --git a/Lib/test/test_py3kwarn.py b/Lib/test/test_py3kwarn.py index 2ea08c5491c662..5168e37184a017 100644 --- a/Lib/test/test_py3kwarn.py +++ b/Lib/test/test_py3kwarn.py @@ -221,6 +221,34 @@ def test_sort_cmp_arg(self): w.reset() self.assertWarning(sorted(lst, cmp), w, expected) + def test_next_method(self): + expected = 'iterator.next() is not supported in 3.x' + it = iter(range(5)) + with check_py3k_warnings() as w: + self.assertWarning(it.next(), w, expected) + + def test_intern(self): + expected = 'intern() is not supported in 3.x: use sys.intern() instead' + with check_py3k_warnings() as w: + self.assertWarning(intern('pygrate-next-method'), w, expected) + + def test_range_materialization(self): + expected = 'range() may require list materialization in 3.x' + with check_py3k_warnings() as w: + self.assertWarning(range(5) + [5], w, expected) + + def test_xrange_materialization(self): + expected = 'xrange() may require list materialization in 3.x' + with check_py3k_warnings() as w: + items = xrange(5) + self.assertWarning(None, w, expected) + + def test_dict_listlike_materialization(self): + expected = 'dict.keys() may require list materialization in 3.x' + d = {'a': 1, 'b': 2} + with check_py3k_warnings() as w: + self.assertWarning(d.keys()[0], w, expected) + def test_sys_exc_clear(self): expected = 'sys.exc_clear() not supported in 3.x; use except clauses' with check_py3k_warnings() as w: @@ -288,9 +316,10 @@ def test_bytesio_truncate(self): from io import BytesIO x = BytesIO(b'AAAAAA') expected = "BytesIO.truncate() does not shift the file pointer: use seek(0) before doing truncate(0)" - self.assertWarning(x.truncate(0), w, expected) - w.reset() - self.assertNoWarning(x.truncate(-1), w) + with check_py3k_warnings() as w: + self.assertWarning(x.truncate(0), w, expected) + w.reset() + self.assertNoWarning(x.truncate(-1), w) def test_file_open(self): expected = ("The builtin 'file()'/'open()' function is not supported in 3.x, " diff --git a/Objects/dictobject.c b/Objects/dictobject.c index c15c5f32167b91..7d6c33cb44acc6 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -8,6 +8,8 @@ */ #include "Python.h" +#include "frameobject.h" +#include "opcode.h" /* Set a key error with the specified argument, wrapping it in a @@ -27,6 +29,57 @@ set_key_error(PyObject *arg) /* Define this out if you don't want conversion statistics on exit. */ #undef SHOW_CONVERSION_COUNTS +static int +py3kwarn_next_opcode(void) +{ + PyFrameObject *frame; + char *code; + Py_ssize_t n; + int offset; + int op; + int steps; + + frame = PyEval_GetFrame(); + if (frame == NULL || frame->f_code == NULL) + return -1; + if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { + PyErr_Clear(); + return -1; + } + + offset = frame->f_lasti; + if (offset < 0 || offset >= n) + return -1; + + op = (unsigned char)code[offset]; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + + for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { + op = (unsigned char)code[offset]; + if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || + op == BINARY_SUBSCR || op == STORE_SUBSCR) + return op; + if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || + op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) + return -1; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + } + return -1; +} + +#define WARN_DICT_LISTLIKE(MSG) \ + do { \ + int nextop = py3kwarn_next_opcode(); \ + if ((nextop == BINARY_SUBSCR || nextop == STORE_SUBSCR || \ + nextop == BINARY_ADD || nextop == INPLACE_ADD) && \ + PyErr_WarnPy3k((MSG), 1) < 0) \ + return NULL; \ + } while (0) + /* See large comment block below. This must be >= 1. */ #define PERTURB_SHIFT 5 @@ -1301,6 +1354,8 @@ dict_keys(register PyDictObject *mp) PyDictEntry *ep; Py_ssize_t mask, n; + WARN_DICT_LISTLIKE("dict.keys() may require list materialization in 3.x"); + again: n = mp->ma_used; v = PyList_New(n); @@ -1335,6 +1390,8 @@ dict_values(register PyDictObject *mp) PyDictEntry *ep; Py_ssize_t mask, n; + WARN_DICT_LISTLIKE("dict.values() may require list materialization in 3.x"); + again: n = mp->ma_used; v = PyList_New(n); @@ -1370,6 +1427,8 @@ dict_items(register PyDictObject *mp) PyObject *item, *key, *value; PyDictEntry *ep; + WARN_DICT_LISTLIKE("dict.items() may require list materialization in 3.x"); + /* Preallocate the list of tuples, to avoid allocations during * the loop over the items, which could trigger GC, which * could resize the dict. :-( diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index d21d1627312d65..b94761896f28f6 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -1,6 +1,8 @@ /* Range object implementation */ #include "Python.h" +#include "frameobject.h" +#include "opcode.h" typedef struct { PyObject_HEAD @@ -9,6 +11,48 @@ typedef struct { long len; } rangeobject; +static int +py3kwarn_next_opcode(void) +{ + PyFrameObject *frame; + char *code; + Py_ssize_t n; + int offset; + int op; + int steps; + + frame = PyEval_GetFrame(); + if (frame == NULL || frame->f_code == NULL) + return -1; + if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { + PyErr_Clear(); + return -1; + } + + offset = frame->f_lasti; + if (offset < 0 || offset >= n) + return -1; + + op = (unsigned char)code[offset]; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + + for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { + op = (unsigned char)code[offset]; + if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || + op == BINARY_SUBSCR || op == STORE_SUBSCR) + return op; + if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || + op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) + return -1; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + } + return -1; +} + /* Return number of items in range (lo, hi, step). step != 0 * required. The result always fits in an unsigned long. */ @@ -67,9 +111,18 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw) rangeobject *obj; long ilow = 0, ihigh = 0, istep = 1; unsigned long n; + int nextop; - if (PyErr_WarnPy3k_WithFix("xrange() is not supported in 3.x", "use range() instead", 1) < 0) - return NULL; + nextop = py3kwarn_next_opcode(); + if (nextop == GET_ITER) { + if (PyErr_WarnPy3k_WithFix("xrange() is not supported in 3.x", + "use range() instead", 1) < 0) + return NULL; + } + else { + if (PyErr_WarnPy3k("xrange() may require list materialization in 3.x", 1) < 0) + return NULL; + } if (!_PyArg_NoKeywords("xrange()", kw)) return NULL; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 1c8958c49a3bf4..a1ef9fea94790e 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4857,6 +4857,10 @@ wrap_next(PyObject *self, PyObject *args, void *wrapped) if (!check_num_args(args, 0)) return NULL; + if (Py_TYPE(self)->tp_iter != NULL) { + if (PyErr_WarnPy3k("iterator.next() is not supported in 3.x", 1) < 0) + return NULL; + } res = (*func)(self); if (res == NULL && !PyErr_Occurred()) PyErr_SetNone(PyExc_StopIteration); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 1275c027cce4eb..07c0d5778b467d 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2,10 +2,12 @@ #include "Python.h" #include "Python-ast.h" +#include "frameobject.h" #include "node.h" #include "code.h" #include "eval.h" +#include "opcode.h" #include #include /* for DBL_MANT_DIG and friends */ @@ -32,6 +34,48 @@ static PyObject *filterunicode(PyObject *, PyObject *); #endif static PyObject *filtertuple (PyObject *, PyObject *); +static int +py3kwarn_next_opcode(void) +{ + PyFrameObject *frame; + char *code; + Py_ssize_t n; + int offset; + int op; + int steps; + + frame = PyEval_GetFrame(); + if (frame == NULL || frame->f_code == NULL) + return -1; + if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { + PyErr_Clear(); + return -1; + } + + offset = frame->f_lasti; + if (offset < 0 || offset >= n) + return -1; + + op = (unsigned char)code[offset]; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + + for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { + op = (unsigned char)code[offset]; + if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || + op == BINARY_SUBSCR || op == STORE_SUBSCR) + return op; + if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || + op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) + return -1; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + } + return -1; +} + static PyObject * builtin___import__(PyObject *self, PyObject *args, PyObject *kwds) { @@ -1282,6 +1326,9 @@ builtin_intern(PyObject *self, PyObject *args) "can't intern subclass of string"); return NULL; } + if (PyErr_WarnPy3k("intern() is not supported in 3.x: use sys.intern() instead", + 1) < 0) + return NULL; Py_INCREF(s); PyString_InternInPlace(&s); return s; @@ -1970,9 +2017,15 @@ builtin_range(PyObject *self, PyObject *args) long ilow = 0, ihigh = 0, istep = 1; long bign; Py_ssize_t i, n; + int nextop; PyObject *v; + nextop = py3kwarn_next_opcode(); + if ((nextop == BINARY_ADD || nextop == INPLACE_ADD) && + PyErr_WarnPy3k("range() may require list materialization in 3.x", 1) < 0) + return NULL; + if (PyTuple_Size(args) <= 1) { if (!PyArg_ParseTuple(args, "l;range() requires 1-3 int arguments", From 305a28936796e47222ad65186aa30af0954ad25c Mon Sep 17 00:00:00 2001 From: Eddy Xu Date: Wed, 18 Mar 2026 17:23:44 -0400 Subject: [PATCH 2/3] add warnings message fix some design issues. --- Include/ceval.h | 1 + Include/warnings.h | 1 - Lib/test/test_py3kwarn.py | 2 +- Objects/dictobject.c | 45 +---------------------------- Objects/rangeobject.c | 59 +++++---------------------------------- Objects/typeobject.c | 8 +++--- Python/bltinmodule.c | 45 +---------------------------- Python/ceval.c | 47 +++++++++++++++++++++++++++++++ 8 files changed, 62 insertions(+), 146 deletions(-) diff --git a/Include/ceval.h b/Include/ceval.h index f46160158e52af..d637e0c5b647ef 100644 --- a/Include/ceval.h +++ b/Include/ceval.h @@ -30,6 +30,7 @@ PyAPI_FUNC(PyObject *) PyEval_GetGlobals(void); PyAPI_FUNC(PyObject *) PyEval_GetLocals(void); PyAPI_FUNC(struct _frame *) PyEval_GetFrame(void); PyAPI_FUNC(int) PyEval_GetRestricted(void); +PyAPI_FUNC(int) _Py3kWarn_NextOpcode(void); /* Look at the current frame's (if any) code's co_flags, and turn on the corresponding compiler flags in cf->cf_flags. Return 1 if any diff --git a/Include/warnings.h b/Include/warnings.h index 08bee92d7d9cfb..71bec8830f5eab 100644 --- a/Include/warnings.h +++ b/Include/warnings.h @@ -26,4 +26,3 @@ PyAPI_FUNC(int) PyErr_WarnExplicit_WithFix(PyObject *, const char *, const char } #endif #endif /* !Py_WARNINGS_H */ - diff --git a/Lib/test/test_py3kwarn.py b/Lib/test/test_py3kwarn.py index 5168e37184a017..afd3c1b420bce3 100644 --- a/Lib/test/test_py3kwarn.py +++ b/Lib/test/test_py3kwarn.py @@ -222,7 +222,7 @@ def test_sort_cmp_arg(self): self.assertWarning(sorted(lst, cmp), w, expected) def test_next_method(self): - expected = 'iterator.next() is not supported in 3.x' + expected = 'iterator.next() is not supported in 3.x; use __next__() instead' it = iter(range(5)) with check_py3k_warnings() as w: self.assertWarning(it.next(), w, expected) diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 7d6c33cb44acc6..936627a9cffc81 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -8,7 +8,6 @@ */ #include "Python.h" -#include "frameobject.h" #include "opcode.h" @@ -29,51 +28,9 @@ set_key_error(PyObject *arg) /* Define this out if you don't want conversion statistics on exit. */ #undef SHOW_CONVERSION_COUNTS -static int -py3kwarn_next_opcode(void) -{ - PyFrameObject *frame; - char *code; - Py_ssize_t n; - int offset; - int op; - int steps; - - frame = PyEval_GetFrame(); - if (frame == NULL || frame->f_code == NULL) - return -1; - if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { - PyErr_Clear(); - return -1; - } - - offset = frame->f_lasti; - if (offset < 0 || offset >= n) - return -1; - - op = (unsigned char)code[offset]; - offset += 1; - if (HAS_ARG(op)) - offset += 2; - - for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { - op = (unsigned char)code[offset]; - if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || - op == BINARY_SUBSCR || op == STORE_SUBSCR) - return op; - if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || - op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) - return -1; - offset += 1; - if (HAS_ARG(op)) - offset += 2; - } - return -1; -} - #define WARN_DICT_LISTLIKE(MSG) \ do { \ - int nextop = py3kwarn_next_opcode(); \ + int nextop = _Py3kWarn_NextOpcode(); \ if ((nextop == BINARY_SUBSCR || nextop == STORE_SUBSCR || \ nextop == BINARY_ADD || nextop == INPLACE_ADD) && \ PyErr_WarnPy3k((MSG), 1) < 0) \ diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index b94761896f28f6..0e62f7bbdbf8f0 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -1,7 +1,6 @@ /* Range object implementation */ #include "Python.h" -#include "frameobject.h" #include "opcode.h" typedef struct { @@ -11,48 +10,6 @@ typedef struct { long len; } rangeobject; -static int -py3kwarn_next_opcode(void) -{ - PyFrameObject *frame; - char *code; - Py_ssize_t n; - int offset; - int op; - int steps; - - frame = PyEval_GetFrame(); - if (frame == NULL || frame->f_code == NULL) - return -1; - if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { - PyErr_Clear(); - return -1; - } - - offset = frame->f_lasti; - if (offset < 0 || offset >= n) - return -1; - - op = (unsigned char)code[offset]; - offset += 1; - if (HAS_ARG(op)) - offset += 2; - - for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { - op = (unsigned char)code[offset]; - if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || - op == BINARY_SUBSCR || op == STORE_SUBSCR) - return op; - if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || - op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) - return -1; - offset += 1; - if (HAS_ARG(op)) - offset += 2; - } - return -1; -} - /* Return number of items in range (lo, hi, step). step != 0 * required. The result always fits in an unsigned long. */ @@ -113,16 +70,14 @@ range_new(PyTypeObject *type, PyObject *args, PyObject *kw) unsigned long n; int nextop; - nextop = py3kwarn_next_opcode(); - if (nextop == GET_ITER) { - if (PyErr_WarnPy3k_WithFix("xrange() is not supported in 3.x", + nextop = _Py3kWarn_NextOpcode(); + if (nextop == GET_ITER && + PyErr_WarnPy3k_WithFix("xrange() is not supported in 3.x", "use range() instead", 1) < 0) - return NULL; - } - else { - if (PyErr_WarnPy3k("xrange() may require list materialization in 3.x", 1) < 0) - return NULL; - } + return NULL; + if (nextop != GET_ITER && + PyErr_WarnPy3k("xrange() may require list materialization in 3.x", 1) < 0) + return NULL; if (!_PyArg_NoKeywords("xrange()", kw)) return NULL; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index a1ef9fea94790e..e1a5b8eff86b13 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4857,10 +4857,10 @@ wrap_next(PyObject *self, PyObject *args, void *wrapped) if (!check_num_args(args, 0)) return NULL; - if (Py_TYPE(self)->tp_iter != NULL) { - if (PyErr_WarnPy3k("iterator.next() is not supported in 3.x", 1) < 0) - return NULL; - } + if (Py_TYPE(self)->tp_iter != NULL && + PyErr_WarnPy3k("iterator.next() is not supported in 3.x; " + "use __next__() instead", 1) < 0) + return NULL; res = (*func)(self); if (res == NULL && !PyErr_Occurred()) PyErr_SetNone(PyExc_StopIteration); diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 07c0d5778b467d..f2ec830f7a43e9 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -2,7 +2,6 @@ #include "Python.h" #include "Python-ast.h" -#include "frameobject.h" #include "node.h" #include "code.h" @@ -34,48 +33,6 @@ static PyObject *filterunicode(PyObject *, PyObject *); #endif static PyObject *filtertuple (PyObject *, PyObject *); -static int -py3kwarn_next_opcode(void) -{ - PyFrameObject *frame; - char *code; - Py_ssize_t n; - int offset; - int op; - int steps; - - frame = PyEval_GetFrame(); - if (frame == NULL || frame->f_code == NULL) - return -1; - if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { - PyErr_Clear(); - return -1; - } - - offset = frame->f_lasti; - if (offset < 0 || offset >= n) - return -1; - - op = (unsigned char)code[offset]; - offset += 1; - if (HAS_ARG(op)) - offset += 2; - - for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { - op = (unsigned char)code[offset]; - if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || - op == BINARY_SUBSCR || op == STORE_SUBSCR) - return op; - if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || - op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) - return -1; - offset += 1; - if (HAS_ARG(op)) - offset += 2; - } - return -1; -} - static PyObject * builtin___import__(PyObject *self, PyObject *args, PyObject *kwds) { @@ -2021,7 +1978,7 @@ builtin_range(PyObject *self, PyObject *args) PyObject *v; - nextop = py3kwarn_next_opcode(); + nextop = _Py3kWarn_NextOpcode(); if ((nextop == BINARY_ADD || nextop == INPLACE_ADD) && PyErr_WarnPy3k("range() may require list materialization in 3.x", 1) < 0) return NULL; diff --git a/Python/ceval.c b/Python/ceval.c index e1140a8e401243..2af1ef7a42fed6 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -19,6 +19,53 @@ #include +int +_Py3kWarn_NextOpcode(void) +{ + PyFrameObject *frame; + char *code; + Py_ssize_t n; + int offset; + int op; + int steps; + + frame = PyEval_GetFrame(); + if (frame == NULL || frame->f_code == NULL) + return -1; + if (PyString_AsStringAndSize(frame->f_code->co_code, &code, &n) < 0) { + PyErr_Clear(); + return -1; + } + + offset = frame->f_lasti; + if (offset < 0 || offset >= n) + return -1; + + op = (unsigned char)code[offset]; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + + /* These warnings only care about the immediate consumer of the + just-evaluated call result. Looking ahead a handful of opcodes is + enough to skip trivial stack setup before we either find a relevant + consumer or hit a stop opcode showing the value has already been + stored, returned, or discarded. */ + for (steps = 0; steps < 8 && offset >= 0 && offset < n; steps++) { + op = (unsigned char)code[offset]; + if (op == BINARY_ADD || op == INPLACE_ADD || op == GET_ITER || + op == BINARY_SUBSCR || op == STORE_SUBSCR) + return op; + if (op == RETURN_VALUE || op == STORE_NAME || op == STORE_FAST || + op == STORE_GLOBAL || op == STORE_ATTR || op == POP_TOP) + return -1; + offset += 1; + if (HAS_ARG(op)) + offset += 2; + } + return -1; +} + #ifndef WITH_TSC #define READ_TIMESTAMP(var) From d74b6ef0fb69428a015b7acfb2aea6e8d639eb01 Mon Sep 17 00:00:00 2001 From: Eddy Xu Date: Wed, 18 Mar 2026 17:24:54 -0400 Subject: [PATCH 3/3] fix --- Include/warnings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/Include/warnings.h b/Include/warnings.h index 71bec8830f5eab..08bee92d7d9cfb 100644 --- a/Include/warnings.h +++ b/Include/warnings.h @@ -26,3 +26,4 @@ PyAPI_FUNC(int) PyErr_WarnExplicit_WithFix(PyObject *, const char *, const char } #endif #endif /* !Py_WARNINGS_H */ +