From fcd1a057cfcf6011f1a97424ec95a4faaa54ca2e Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Thu, 8 Jun 2017 23:17:04 +0530 Subject: [PATCH 01/11] bpo-30597: Shows expected input in custom 'print' error message --- Objects/exceptions.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 858eff5fc26c34..48184e0e5b7583 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2897,8 +2897,24 @@ _check_for_legacy_statements(PySyntaxErrorObject *self, Py_ssize_t start) } if (PyUnicode_Tailmatch(self->text, print_prefix, start, text_len, -1)) { - Py_XSETREF(self->msg, - PyUnicode_FromString("Missing parentheses in call to 'print'")); + char *error_msg_start = "Missing parentheses in call to 'print'. Did you mean 'print("; + // PRINT_OFFSET is to remove print word from the data. + const int PRINT_OFFSET = 6; + char *msg = data + PRINT_OFFSET; + char *error_msg_end = ")'?"; + + char *error_msg = malloc(strlen(error_msg_start) + \ + text_len + \ + strlen(error_msg_end)) + 1; + + strcat(error_msg, error_msg_start); + strcat(error_msg, msg); + strcat(error_msg, error_msg_end); + + Py_XSETREF( + self->msg, + PyUnicode_FromString(error_msg) + ); return 1; } From 737ff2863ac92cf1c6ef0fdc0d107c5ba96b011a Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Fri, 9 Jun 2017 14:37:32 +0530 Subject: [PATCH 02/11] bpo-30597: Address some review comments. Still WIP --- Objects/exceptions.c | 67 +++++++++++++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 48184e0e5b7583..3be91146fac344 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2862,6 +2862,52 @@ _PyErr_TrySetFromCause(const char *format, ...) * or minus, using the stream redirection syntax). */ + +// Static helper for setting legacy print error message +static int +_set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) +{ + Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text); + + /* + `data` is UTF-8 encoded and treated as an indivisible opaque blob. All + we know about it is that it starts with `print ` and may end with a `,` + in which case we also append suggest_end_arg which makes the input + analogous to Python3 print syntax. + */ + void *data = PyUnicode_DATA(self->text); + char *error_msg_start = "Missing parentheses in call to 'print'. Did you mean 'print("; + // PRINT_OFFSET is to remove print word from the data. + const int PRINT_OFFSET = 6; + char *msg = data + PRINT_OFFSET; + text_len = text_len - PRINT_OFFSET - 1; + // strips off newline character from msg + msg[strcspn(msg, "\r\n")] = 0; + char *error_msg_end = ")'?"; + + char *suggest_end_arg = " end=' '"; + + char *error_msg = malloc( + strlen(error_msg_start) + \ + text_len + \ + strlen(suggest_end_arg) + \ + strlen(error_msg_end) + ) + 1; + + strcat(error_msg, error_msg_start); + strcat(error_msg, msg); + if(strcmp(&msg[text_len-1], ",") == 0) + strcat(error_msg, suggest_end_arg); + strcat(error_msg, error_msg_end); + + Py_XSETREF( + self->msg, + PyUnicode_FromString(error_msg) + ); +// free(error_msg); + return 1; +} + static int _check_for_legacy_statements(PySyntaxErrorObject *self, Py_ssize_t start) { @@ -2897,25 +2943,8 @@ _check_for_legacy_statements(PySyntaxErrorObject *self, Py_ssize_t start) } if (PyUnicode_Tailmatch(self->text, print_prefix, start, text_len, -1)) { - char *error_msg_start = "Missing parentheses in call to 'print'. Did you mean 'print("; - // PRINT_OFFSET is to remove print word from the data. - const int PRINT_OFFSET = 6; - char *msg = data + PRINT_OFFSET; - char *error_msg_end = ")'?"; - - char *error_msg = malloc(strlen(error_msg_start) + \ - text_len + \ - strlen(error_msg_end)) + 1; - - strcat(error_msg, error_msg_start); - strcat(error_msg, msg); - strcat(error_msg, error_msg_end); - - Py_XSETREF( - self->msg, - PyUnicode_FromString(error_msg) - ); - return 1; + + return _set_legacy_print_statement_msg(self, start); } /* Check for legacy exec statements */ From 3d08859f6f8001996b739e8b7c2016f6618a53f5 Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Fri, 9 Jun 2017 18:09:01 +0530 Subject: [PATCH 03/11] bpo-30597: Re-writes patch using already available utilities --- Objects/exceptions.c | 48 +++++++++++++++++--------------------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 3be91146fac344..8873c6f7b9e2c6 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2867,44 +2867,32 @@ _PyErr_TrySetFromCause(const char *format, ...) static int _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) { - Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text); - /* `data` is UTF-8 encoded and treated as an indivisible opaque blob. All we know about it is that it starts with `print ` and may end with a `,` in which case we also append suggest_end_arg which makes the input analogous to Python3 print syntax. */ - void *data = PyUnicode_DATA(self->text); - char *error_msg_start = "Missing parentheses in call to 'print'. Did you mean 'print("; + + PyObject *strip_sep_obj = PyUnicode_FromString("\r\n"); + PyObject *data = _PyUnicode_XStrip(self->text, 1, strip_sep_obj); // PRINT_OFFSET is to remove print word from the data. const int PRINT_OFFSET = 6; - char *msg = data + PRINT_OFFSET; - text_len = text_len - PRINT_OFFSET - 1; - // strips off newline character from msg - msg[strcspn(msg, "\r\n")] = 0; - char *error_msg_end = ")'?"; - - char *suggest_end_arg = " end=' '"; - - char *error_msg = malloc( - strlen(error_msg_start) + \ - text_len + \ - strlen(suggest_end_arg) + \ - strlen(error_msg_end) - ) + 1; - - strcat(error_msg, error_msg_start); - strcat(error_msg, msg); - if(strcmp(&msg[text_len-1], ",") == 0) - strcat(error_msg, suggest_end_arg); - strcat(error_msg, error_msg_end); - - Py_XSETREF( - self->msg, - PyUnicode_FromString(error_msg) - ); -// free(error_msg); + Py_ssize_t text_len = PyUnicode_GET_LENGTH(data); + data = PyUnicode_Substring(data, PRINT_OFFSET, text_len); + // gets the modified text_len after stripping print `` + text_len = PyUnicode_GET_LENGTH(data); + char *maybe_end_arg = " end=' '"; + + PyObject *error_msg = PyUnicode_FromFormat( + "Missing parentheses in call to 'print'. Did you mean print(%U)?", + data); + if (PyUnicode_Tailmatch(data, PyUnicode_FromString(","), start, text_len, 1)) + error_msg = PyUnicode_FromFormat( + "Missing parentheses in call to 'print'. Did you mean print(%U%s)?", + data, maybe_end_arg); + + Py_XSETREF(self->msg, error_msg); return 1; } From 80d96b280e567c7e4682b513df0d35075f224d2f Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Sat, 10 Jun 2017 00:52:50 +0530 Subject: [PATCH 04/11] bpo-30597: Includes unittest for Py2 migration hint on print statement --- Lib/test/test_print.py | 22 ++++++++++++++++++++++ Objects/exceptions.c | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_print.py b/Lib/test/test_print.py index 7eea349115a947..a3d550d7a035f6 100644 --- a/Lib/test/test_print.py +++ b/Lib/test/test_print.py @@ -128,5 +128,27 @@ def flush(self): raise RuntimeError self.assertRaises(RuntimeError, print, 1, file=noflush(), flush=True) + +class TestPy2MigrationHint(unittest.TestCase): + """Test that correct hint is produced analogous to Python3 syntax, + if print statement is executed as in Python 2. + """ + + def test_normal_string(self): + python2_print_str = 'print "Hello World"' + with self.assertRaises(SyntaxError) as context: + exec(python2_print_str) + + self.assertTrue('print("Hello World")' in str(context.exception)) + + def test_string_with_soft_space(self): + python2_print_str = 'print "Hello World",' + with self.assertRaises(SyntaxError) as context: + exec(python2_print_str) + + print(str(context.exception)) + self.assertTrue('print("Hello World", end=" ")' in str(context.exception)) + + if __name__ == "__main__": unittest.main() diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 8873c6f7b9e2c6..21a62f2c8389a2 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2880,9 +2880,9 @@ _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) const int PRINT_OFFSET = 6; Py_ssize_t text_len = PyUnicode_GET_LENGTH(data); data = PyUnicode_Substring(data, PRINT_OFFSET, text_len); - // gets the modified text_len after stripping print `` + // gets the modified text_len after stripping `print ` text_len = PyUnicode_GET_LENGTH(data); - char *maybe_end_arg = " end=' '"; + char *maybe_end_arg = " end=\" \""; PyObject *error_msg = PyUnicode_FromFormat( "Missing parentheses in call to 'print'. Did you mean print(%U)?", From 07d29430e0d9ef6bccd236f935901ab55e7f5989 Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Tue, 13 Jun 2017 10:32:53 +0530 Subject: [PATCH 05/11] bpo-30597: Address review comments --- Objects/exceptions.c | 45 +++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 21a62f2c8389a2..74b2b42a8ba930 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2867,32 +2867,43 @@ _PyErr_TrySetFromCause(const char *format, ...) static int _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) { - /* - `data` is UTF-8 encoded and treated as an indivisible opaque blob. All - we know about it is that it starts with `print ` and may end with a `,` - in which case we also append suggest_end_arg which makes the input - analogous to Python3 print syntax. - */ PyObject *strip_sep_obj = PyUnicode_FromString("\r\n"); - PyObject *data = _PyUnicode_XStrip(self->text, 1, strip_sep_obj); - // PRINT_OFFSET is to remove print word from the data. + Py_UCS4 soft_space_check = PyUnicode_READ_CHAR(PyUnicode_FromString(","), 0); + + if (strip_sep_obj == NULL) + return -1; + + // PRINT_OFFSET is to remove `print ` word from the data. const int PRINT_OFFSET = 6; - Py_ssize_t text_len = PyUnicode_GET_LENGTH(data); - data = PyUnicode_Substring(data, PRINT_OFFSET, text_len); + Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text); + PyObject *data = PyUnicode_Substring(self->text, PRINT_OFFSET, text_len); + + if (data == NULL) + return -1; + + data = _PyUnicode_XStrip(data, 1, strip_sep_obj); + + Py_DECREF(strip_sep_obj); + // gets the modified text_len after stripping `print ` text_len = PyUnicode_GET_LENGTH(data); - char *maybe_end_arg = " end=\" \""; + const char *maybe_end_arg = ""; + + if (text_len > 0) { + if (PyUnicode_READ_CHAR(data, text_len-1) == soft_space_check) { + maybe_end_arg = " end=\" \""; + } + } PyObject *error_msg = PyUnicode_FromFormat( - "Missing parentheses in call to 'print'. Did you mean print(%U)?", - data); - if (PyUnicode_Tailmatch(data, PyUnicode_FromString(","), start, text_len, 1)) - error_msg = PyUnicode_FromFormat( - "Missing parentheses in call to 'print'. Did you mean print(%U%s)?", - data, maybe_end_arg); + "Missing parentheses in call to 'print'. Did you mean print(%U%s)?", + data, maybe_end_arg); + + Py_DECREF(data); Py_XSETREF(self->msg, error_msg); + return 1; } From 17119758becec99e2fa31f0e5f14f67c72bea5ac Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Tue, 13 Jun 2017 10:53:49 +0530 Subject: [PATCH 06/11] bpo-30597: Minor change --- Lib/test/test_print.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_print.py b/Lib/test/test_print.py index a3d550d7a035f6..53e8f7893cb735 100644 --- a/Lib/test/test_print.py +++ b/Lib/test/test_print.py @@ -146,7 +146,6 @@ def test_string_with_soft_space(self): with self.assertRaises(SyntaxError) as context: exec(python2_print_str) - print(str(context.exception)) self.assertTrue('print("Hello World", end=" ")' in str(context.exception)) From 99ad8da1b1e79acef60717558d5c9801d00ee479 Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Tue, 13 Jun 2017 13:17:20 +0530 Subject: [PATCH 07/11] bpo-30597: Address review comments --- Objects/exceptions.c | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 74b2b42a8ba930..6d4f095a3fa34c 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2868,8 +2868,8 @@ static int _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) { - PyObject *strip_sep_obj = PyUnicode_FromString("\r\n"); - Py_UCS4 soft_space_check = PyUnicode_READ_CHAR(PyUnicode_FromString(","), 0); + PyObject *strip_sep_obj = PyUnicode_FromString(" \t\r\n"); +// Py_UCS4 soft_space_check = PyUnicode_READ_CHAR(PyUnicode_FromString(","), 0); if (strip_sep_obj == NULL) return -1; @@ -2879,28 +2879,37 @@ _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text); PyObject *data = PyUnicode_Substring(self->text, PRINT_OFFSET, text_len); - if (data == NULL) + if (data == NULL) { + Py_DECREF(strip_sep_obj); return -1; + } - data = _PyUnicode_XStrip(data, 1, strip_sep_obj); + PyObject *new_data = _PyUnicode_XStrip(data, 1, strip_sep_obj); + Py_DECREF(data); Py_DECREF(strip_sep_obj); + if (new_data == NULL) { + return -1; + } + // gets the modified text_len after stripping `print ` - text_len = PyUnicode_GET_LENGTH(data); + text_len = PyUnicode_GET_LENGTH(new_data); const char *maybe_end_arg = ""; - if (text_len > 0) { - if (PyUnicode_READ_CHAR(data, text_len-1) == soft_space_check) { - maybe_end_arg = " end=\" \""; - } + if (text_len > 0 && PyUnicode_READ_CHAR(new_data, text_len-1) == ',') { + maybe_end_arg = " end=\" \""; } PyObject *error_msg = PyUnicode_FromFormat( "Missing parentheses in call to 'print'. Did you mean print(%U%s)?", - data, maybe_end_arg); + new_data, maybe_end_arg + ); - Py_DECREF(data); + Py_DECREF(new_data); + + if (error_msg == NULL) + return -1; Py_XSETREF(self->msg, error_msg); From 0a1de7d9b614a67bd6a3d1d8689120b8cdc39548 Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Tue, 13 Jun 2017 13:19:40 +0530 Subject: [PATCH 08/11] bpo-30597: Remove commented code --- Objects/exceptions.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 6d4f095a3fa34c..82f34500520a6a 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2869,7 +2869,6 @@ _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) { PyObject *strip_sep_obj = PyUnicode_FromString(" \t\r\n"); -// Py_UCS4 soft_space_check = PyUnicode_READ_CHAR(PyUnicode_FromString(","), 0); if (strip_sep_obj == NULL) return -1; From 3401409b9b682859369b23c0f620731ae356500c Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Tue, 20 Jun 2017 16:58:32 +0530 Subject: [PATCH 09/11] bpo-30597: Address Review comments --- Lib/test/test_print.py | 11 +++++++++-- Misc/NEWS | 5 ++++- Objects/exceptions.c | 12 +----------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_print.py b/Lib/test/test_print.py index 53e8f7893cb735..996fd550cefce9 100644 --- a/Lib/test/test_print.py +++ b/Lib/test/test_print.py @@ -139,14 +139,21 @@ def test_normal_string(self): with self.assertRaises(SyntaxError) as context: exec(python2_print_str) - self.assertTrue('print("Hello World")' in str(context.exception)) + self.assertIn('print("Hello World")', str(context.exception)) def test_string_with_soft_space(self): python2_print_str = 'print "Hello World",' with self.assertRaises(SyntaxError) as context: exec(python2_print_str) - self.assertTrue('print("Hello World", end=" ")' in str(context.exception)) + self.assertIn('print("Hello World", end=" ")', str(context.exception)) + + def test_string_with_excessive_whitespace(self): + python2_print_str = 'print "Hello World", ' + with self.assertRaises(SyntaxError) as context: + exec(python2_print_str) + + self.assertIn('print("Hello World", end=" ")', str(context.exception)) if __name__ == "__main__": diff --git a/Misc/NEWS b/Misc/NEWS index 902b102ac72d5c..a0400a29df81c5 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ What's New in Python 3.7.0 alpha 1? Core and Builtins ----------------- +- bpo-30597: `Print` now shows expected input in custom error message when + used as a Python 2 statement. + - bpo-25324: Tokens needed for parsing in Python moved to C. ``COMMENT``, ``NL`` and ``ENCODING``. This way the tokens and tok_names in the token module don't get changed when you import the tokenize module. @@ -347,7 +350,7 @@ Library - bpo-30245: Fix possible overflow when organize struct.pack_into error message. Patch by Yuan Liu. - + - bpo-30378: Fix the problem that logging.handlers.SysLogHandler cannot handle IPv6 addresses. diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 82f34500520a6a..190ad0654024b0 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -2867,9 +2867,7 @@ _PyErr_TrySetFromCause(const char *format, ...) static int _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) { - PyObject *strip_sep_obj = PyUnicode_FromString(" \t\r\n"); - if (strip_sep_obj == NULL) return -1; @@ -2882,36 +2880,28 @@ _set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start) Py_DECREF(strip_sep_obj); return -1; } - - PyObject *new_data = _PyUnicode_XStrip(data, 1, strip_sep_obj); - + PyObject *new_data = _PyUnicode_XStrip(data, 2, strip_sep_obj); Py_DECREF(data); Py_DECREF(strip_sep_obj); if (new_data == NULL) { return -1; } - // gets the modified text_len after stripping `print ` text_len = PyUnicode_GET_LENGTH(new_data); const char *maybe_end_arg = ""; - if (text_len > 0 && PyUnicode_READ_CHAR(new_data, text_len-1) == ',') { maybe_end_arg = " end=\" \""; } - PyObject *error_msg = PyUnicode_FromFormat( "Missing parentheses in call to 'print'. Did you mean print(%U%s)?", new_data, maybe_end_arg ); - Py_DECREF(new_data); - if (error_msg == NULL) return -1; Py_XSETREF(self->msg, error_msg); - return 1; } From 825741bf5eb0480cd9b2f04ca20c4c05da5055ea Mon Sep 17 00:00:00 2001 From: CuriousLearner Date: Tue, 20 Jun 2017 17:44:46 +0530 Subject: [PATCH 10/11] bpo-30597: Address Review comments --- Lib/test/test_print.py | 2 +- Misc/NEWS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_print.py b/Lib/test/test_print.py index 996fd550cefce9..03f13b4edfc598 100644 --- a/Lib/test/test_print.py +++ b/Lib/test/test_print.py @@ -149,7 +149,7 @@ def test_string_with_soft_space(self): self.assertIn('print("Hello World", end=" ")', str(context.exception)) def test_string_with_excessive_whitespace(self): - python2_print_str = 'print "Hello World", ' + python2_print_str = 'print "Hello World", ' with self.assertRaises(SyntaxError) as context: exec(python2_print_str) diff --git a/Misc/NEWS b/Misc/NEWS index 765f60c7e4dd79..497195dfe96cfe 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -11,7 +11,7 @@ Core and Builtins ----------------- - bpo-30597: `Print` now shows expected input in custom error message when - used as a Python 2 statement. + used as a Python 2 statement. Patch by Sanyam Khurana. - bpo-30682: Removed a too-strict assertion that failed for certain f-strings, such as eval("f'\\\n'") and eval("f'\\\r'"). From d3efcd085d098df5df8b7ce1fa607128bfd3f227 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Tue, 20 Jun 2017 22:18:01 +1000 Subject: [PATCH 11/11] Fix formatting in NEWS entry --- Misc/NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS b/Misc/NEWS index 497195dfe96cfe..b58b580c8b6a76 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,7 +10,7 @@ What's New in Python 3.7.0 alpha 1? Core and Builtins ----------------- -- bpo-30597: `Print` now shows expected input in custom error message when +- bpo-30597: ``print`` now shows expected input in custom error message when used as a Python 2 statement. Patch by Sanyam Khurana. - bpo-30682: Removed a too-strict assertion that failed for certain f-strings,