mirror of
				https://github.com/python/cpython.git
				synced 2025-10-27 03:34:32 +00:00 
			
		
		
		
	bpo-34013: Move the Python 2 hints from the exception constructor to the parser (GH-27392)
This commit is contained in:
		
							parent
							
								
									6948964ecf
								
							
						
					
					
						commit
						ecc3c8e421
					
				
					 5 changed files with 28 additions and 224 deletions
				
			
		|  | @ -848,9 +848,10 @@ expression_without_invalid[expr_ty]: | ||||||
|     | disjunction |     | disjunction | ||||||
|     | lambdef |     | lambdef | ||||||
| invalid_legacy_expression: | invalid_legacy_expression: | ||||||
|     | a=NAME b=expression_without_invalid { |     | a=NAME b=star_expressions { | ||||||
|         _PyPegen_check_legacy_stmt(p, a) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Missing parentheses in call to '%U'.", a->v.Name.id) : NULL} |         _PyPegen_check_legacy_stmt(p, a) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, | ||||||
|   |             "Missing parentheses in call to '%U'. Did you mean %U(...)?", a->v.Name.id, a->v.Name.id) : NULL} | ||||||
|  | 
 | ||||||
| invalid_expression: | invalid_expression: | ||||||
|    | invalid_legacy_expression |    | invalid_legacy_expression | ||||||
|     # !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf" |     # !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf" | ||||||
|  |  | ||||||
|  | @ -168,21 +168,19 @@ def ckmsg(src, msg, exception=SyntaxError): | ||||||
|                 self.fail("failed to get expected SyntaxError") |                 self.fail("failed to get expected SyntaxError") | ||||||
| 
 | 
 | ||||||
|         s = '''print "old style"''' |         s = '''print "old style"''' | ||||||
|         ckmsg(s, "Missing parentheses in call to 'print'. " |         ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?") | ||||||
|                  "Did you mean print(\"old style\")?") |  | ||||||
| 
 | 
 | ||||||
|         s = '''print "old style",''' |         s = '''print "old style",''' | ||||||
|         ckmsg(s, "Missing parentheses in call to 'print'. " |         ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?") | ||||||
|                  "Did you mean print(\"old style\", end=\" \")?") |  | ||||||
| 
 | 
 | ||||||
|         s = 'print f(a+b,c)' |         s = 'print f(a+b,c)' | ||||||
|         ckmsg(s, "Missing parentheses in call to 'print'.") |         ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?") | ||||||
| 
 | 
 | ||||||
|         s = '''exec "old style"''' |         s = '''exec "old style"''' | ||||||
|         ckmsg(s, "Missing parentheses in call to 'exec'") |         ckmsg(s, "Missing parentheses in call to 'exec'. Did you mean exec(...)?") | ||||||
| 
 | 
 | ||||||
|         s = 'exec f(a+b,c)' |         s = 'exec f(a+b,c)' | ||||||
|         ckmsg(s, "Missing parentheses in call to 'exec'.") |         ckmsg(s, "Missing parentheses in call to 'exec'. Did you mean exec(...)?") | ||||||
| 
 | 
 | ||||||
|         # should not apply to subclasses, see issue #31161 |         # should not apply to subclasses, see issue #31161 | ||||||
|         s = '''if True:\nprint "No indent"''' |         s = '''if True:\nprint "No indent"''' | ||||||
|  |  | ||||||
|  | @ -140,21 +140,24 @@ def test_normal_string(self): | ||||||
|         with self.assertRaises(SyntaxError) as context: |         with self.assertRaises(SyntaxError) as context: | ||||||
|             exec(python2_print_str) |             exec(python2_print_str) | ||||||
| 
 | 
 | ||||||
|         self.assertIn('print("Hello World")', str(context.exception)) |         self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", | ||||||
|  |                 str(context.exception)) | ||||||
| 
 | 
 | ||||||
|     def test_string_with_soft_space(self): |     def test_string_with_soft_space(self): | ||||||
|         python2_print_str = 'print "Hello World",' |         python2_print_str = 'print "Hello World",' | ||||||
|         with self.assertRaises(SyntaxError) as context: |         with self.assertRaises(SyntaxError) as context: | ||||||
|             exec(python2_print_str) |             exec(python2_print_str) | ||||||
| 
 | 
 | ||||||
|         self.assertIn('print("Hello World", end=" ")', str(context.exception)) |         self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", | ||||||
|  |                 str(context.exception)) | ||||||
| 
 | 
 | ||||||
|     def test_string_with_excessive_whitespace(self): |     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: |         with self.assertRaises(SyntaxError) as context: | ||||||
|             exec(python2_print_str) |             exec(python2_print_str) | ||||||
| 
 | 
 | ||||||
|         self.assertIn('print("Hello World", end=" ")', str(context.exception)) |         self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", | ||||||
|  |                 str(context.exception)) | ||||||
| 
 | 
 | ||||||
|     def test_string_with_leading_whitespace(self): |     def test_string_with_leading_whitespace(self): | ||||||
|         python2_print_str = '''if 1: |         python2_print_str = '''if 1: | ||||||
|  | @ -163,7 +166,8 @@ def test_string_with_leading_whitespace(self): | ||||||
|         with self.assertRaises(SyntaxError) as context: |         with self.assertRaises(SyntaxError) as context: | ||||||
|             exec(python2_print_str) |             exec(python2_print_str) | ||||||
| 
 | 
 | ||||||
|         self.assertIn('print("Hello World")', str(context.exception)) |         self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", | ||||||
|  |                 str(context.exception)) | ||||||
| 
 | 
 | ||||||
|     # bpo-32685: Suggestions for print statement should be proper when |     # bpo-32685: Suggestions for print statement should be proper when | ||||||
|     # it is in the same line as the header of a compound statement |     # it is in the same line as the header of a compound statement | ||||||
|  | @ -173,14 +177,16 @@ def test_string_with_semicolon(self): | ||||||
|         with self.assertRaises(SyntaxError) as context: |         with self.assertRaises(SyntaxError) as context: | ||||||
|             exec(python2_print_str) |             exec(python2_print_str) | ||||||
| 
 | 
 | ||||||
|         self.assertIn('print(p)', str(context.exception)) |         self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", | ||||||
|  |                 str(context.exception)) | ||||||
| 
 | 
 | ||||||
|     def test_string_in_loop_on_same_line(self): |     def test_string_in_loop_on_same_line(self): | ||||||
|         python2_print_str = 'for i in s: print i' |         python2_print_str = 'for i in s: print i' | ||||||
|         with self.assertRaises(SyntaxError) as context: |         with self.assertRaises(SyntaxError) as context: | ||||||
|             exec(python2_print_str) |             exec(python2_print_str) | ||||||
| 
 | 
 | ||||||
|         self.assertIn('print(i)', str(context.exception)) |         self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)", | ||||||
|  |                 str(context.exception)) | ||||||
| 
 | 
 | ||||||
|     def test_stream_redirection_hint_for_py2_migration(self): |     def test_stream_redirection_hint_for_py2_migration(self): | ||||||
|         # Test correct hint produced for Py2 redirection syntax |         # Test correct hint produced for Py2 redirection syntax | ||||||
|  |  | ||||||
|  | @ -1475,9 +1475,6 @@ ComplexExtendsException(PyExc_Exception, AttributeError, | ||||||
|  *    SyntaxError extends Exception |  *    SyntaxError extends Exception | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| /* Helper function to customize error message for some syntax errors */ |  | ||||||
| static int _report_missing_parentheses(PySyntaxErrorObject *self); |  | ||||||
| 
 |  | ||||||
| static int | static int | ||||||
| SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds) | SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds) | ||||||
| { | { | ||||||
|  | @ -1520,18 +1517,6 @@ SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds) | ||||||
|             PyErr_SetString(PyExc_TypeError, "end_offset must be provided when end_lineno is provided"); |             PyErr_SetString(PyExc_TypeError, "end_offset must be provided when end_lineno is provided"); | ||||||
|             return -1; |             return -1; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         /*
 |  | ||||||
|          * Issue #21669: Custom error for 'print' & 'exec' as statements |  | ||||||
|          * |  | ||||||
|          * Only applies to SyntaxError instances, not to subclasses such |  | ||||||
|          * as TabError or IndentationError (see issue #31161) |  | ||||||
|          */ |  | ||||||
|         if (Py_IS_TYPE(self, (PyTypeObject *)PyExc_SyntaxError) && |  | ||||||
|                 self->text && PyUnicode_Check(self->text) && |  | ||||||
|                 _report_missing_parentheses(self) < 0) { |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  | @ -3033,189 +3018,3 @@ _PyErr_TrySetFromCause(const char *format, ...) | ||||||
|     PyErr_Restore(new_exc, new_val, new_tb); |     PyErr_Restore(new_exc, new_val, new_tb); | ||||||
|     return new_val; |     return new_val; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| /* To help with migration from Python 2, SyntaxError.__init__ applies some
 |  | ||||||
|  * heuristics to try to report a more meaningful exception when print and |  | ||||||
|  * exec are used like statements. |  | ||||||
|  * |  | ||||||
|  * The heuristics are currently expected to detect the following cases: |  | ||||||
|  *   - top level statement |  | ||||||
|  *   - statement in a nested suite |  | ||||||
|  *   - trailing section of a one line complex statement |  | ||||||
|  * |  | ||||||
|  * They're currently known not to trigger: |  | ||||||
|  *   - after a semi-colon |  | ||||||
|  * |  | ||||||
|  * The error message can be a bit odd in cases where the "arguments" are |  | ||||||
|  * completely illegal syntactically, but that isn't worth the hassle of |  | ||||||
|  * fixing. |  | ||||||
|  * |  | ||||||
|  * We also can't do anything about cases that are legal Python 3 syntax |  | ||||||
|  * but mean something entirely different from what they did in Python 2 |  | ||||||
|  * (omitting the arguments entirely, printing items preceded by a unary plus |  | ||||||
|  * 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) |  | ||||||
| { |  | ||||||
|     // PRINT_OFFSET is to remove the `print ` prefix from the data.
 |  | ||||||
|     const int PRINT_OFFSET = 6; |  | ||||||
|     const int STRIP_BOTH = 2; |  | ||||||
|     Py_ssize_t start_pos = start + PRINT_OFFSET; |  | ||||||
|     Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text); |  | ||||||
|     Py_UCS4 semicolon = ';'; |  | ||||||
|     Py_ssize_t end_pos = PyUnicode_FindChar(self->text, semicolon, |  | ||||||
|                                             start_pos, text_len, 1); |  | ||||||
|     if (end_pos < -1) { |  | ||||||
|       return -1; |  | ||||||
|     } else if (end_pos == -1) { |  | ||||||
|       end_pos = text_len; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     PyObject *data = PyUnicode_Substring(self->text, start_pos, end_pos); |  | ||||||
|     if (data == NULL) { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     PyObject *strip_sep_obj = PyUnicode_FromString(" \t\r\n"); |  | ||||||
|     if (strip_sep_obj == NULL) { |  | ||||||
|         Py_DECREF(data); |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     PyObject *new_data = _PyUnicode_XStrip(data, STRIP_BOTH, 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; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static int |  | ||||||
| _check_for_legacy_statements(PySyntaxErrorObject *self, Py_ssize_t start) |  | ||||||
| { |  | ||||||
|     /* Return values:
 |  | ||||||
|      *   -1: an error occurred |  | ||||||
|      *    0: nothing happened |  | ||||||
|      *    1: the check triggered & the error message was changed |  | ||||||
|      */ |  | ||||||
|     static PyObject *print_prefix = NULL; |  | ||||||
|     static PyObject *exec_prefix = NULL; |  | ||||||
|     Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text), match; |  | ||||||
|     int kind = PyUnicode_KIND(self->text); |  | ||||||
|     const void *data = PyUnicode_DATA(self->text); |  | ||||||
| 
 |  | ||||||
|     /* Ignore leading whitespace */ |  | ||||||
|     while (start < text_len) { |  | ||||||
|         Py_UCS4 ch = PyUnicode_READ(kind, data, start); |  | ||||||
|         if (!Py_UNICODE_ISSPACE(ch)) |  | ||||||
|             break; |  | ||||||
|         start++; |  | ||||||
|     } |  | ||||||
|     /* Checking against an empty or whitespace-only part of the string */ |  | ||||||
|     if (start == text_len) { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /* Check for legacy print statements */ |  | ||||||
|     if (print_prefix == NULL) { |  | ||||||
|         print_prefix = PyUnicode_InternFromString("print "); |  | ||||||
|         if (print_prefix == NULL) { |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     match = PyUnicode_Tailmatch(self->text, print_prefix, |  | ||||||
|                                 start, text_len, -1); |  | ||||||
|     if (match == -1) { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
|     if (match) { |  | ||||||
|         return _set_legacy_print_statement_msg(self, start); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     /* Check for legacy exec statements */ |  | ||||||
|     if (exec_prefix == NULL) { |  | ||||||
|         exec_prefix = PyUnicode_InternFromString("exec "); |  | ||||||
|         if (exec_prefix == NULL) { |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     match = PyUnicode_Tailmatch(self->text, exec_prefix, start, text_len, -1); |  | ||||||
|     if (match == -1) { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
|     if (match) { |  | ||||||
|         PyObject *msg = PyUnicode_FromString("Missing parentheses in call " |  | ||||||
|                                              "to 'exec'"); |  | ||||||
|         if (msg == NULL) { |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|         Py_XSETREF(self->msg, msg); |  | ||||||
|         return 1; |  | ||||||
|     } |  | ||||||
|     /* Fall back to the default error message */ |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static int |  | ||||||
| _report_missing_parentheses(PySyntaxErrorObject *self) |  | ||||||
| { |  | ||||||
|     Py_UCS4 left_paren = 40; |  | ||||||
|     Py_ssize_t left_paren_index; |  | ||||||
|     Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text); |  | ||||||
|     int legacy_check_result = 0; |  | ||||||
| 
 |  | ||||||
|     /* Skip entirely if there is an opening parenthesis */ |  | ||||||
|     left_paren_index = PyUnicode_FindChar(self->text, left_paren, |  | ||||||
|                                           0, text_len, 1); |  | ||||||
|     if (left_paren_index < -1) { |  | ||||||
|         return -1; |  | ||||||
|     } |  | ||||||
|     if (left_paren_index != -1) { |  | ||||||
|         /* Use default error message for any line with an opening paren */ |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|     /* Handle the simple statement case */ |  | ||||||
|     legacy_check_result = _check_for_legacy_statements(self, 0); |  | ||||||
|     if (legacy_check_result < 0) { |  | ||||||
|         return -1; |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
|     if (legacy_check_result == 0) { |  | ||||||
|         /* Handle the one-line complex statement case */ |  | ||||||
|         Py_UCS4 colon = 58; |  | ||||||
|         Py_ssize_t colon_index; |  | ||||||
|         colon_index = PyUnicode_FindChar(self->text, colon, |  | ||||||
|                                          0, text_len, 1); |  | ||||||
|         if (colon_index < -1) { |  | ||||||
|             return -1; |  | ||||||
|         } |  | ||||||
|         if (colon_index >= 0 && colon_index < text_len) { |  | ||||||
|             /* Check again, starting from just after the colon */ |  | ||||||
|             if (_check_for_legacy_statements(self, colon_index+1) < 0) { |  | ||||||
|                 return -1; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|     return 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -18158,7 +18158,7 @@ expression_without_invalid_rule(Parser *p) | ||||||
|     return _res; |     return _res; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // invalid_legacy_expression: NAME expression_without_invalid
 | // invalid_legacy_expression: NAME star_expressions
 | ||||||
| static void * | static void * | ||||||
| invalid_legacy_expression_rule(Parser *p) | invalid_legacy_expression_rule(Parser *p) | ||||||
| { | { | ||||||
|  | @ -18169,22 +18169,22 @@ invalid_legacy_expression_rule(Parser *p) | ||||||
|     } |     } | ||||||
|     void * _res = NULL; |     void * _res = NULL; | ||||||
|     int _mark = p->mark; |     int _mark = p->mark; | ||||||
|     { // NAME expression_without_invalid
 |     { // NAME star_expressions
 | ||||||
|         if (p->error_indicator) { |         if (p->error_indicator) { | ||||||
|             D(p->level--); |             D(p->level--); | ||||||
|             return NULL; |             return NULL; | ||||||
|         } |         } | ||||||
|         D(fprintf(stderr, "%*c> invalid_legacy_expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME expression_without_invalid")); |         D(fprintf(stderr, "%*c> invalid_legacy_expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME star_expressions")); | ||||||
|         expr_ty a; |         expr_ty a; | ||||||
|         expr_ty b; |         expr_ty b; | ||||||
|         if ( |         if ( | ||||||
|             (a = _PyPegen_name_token(p))  // NAME
 |             (a = _PyPegen_name_token(p))  // NAME
 | ||||||
|             && |             && | ||||||
|             (b = expression_without_invalid_rule(p))  // expression_without_invalid
 |             (b = star_expressions_rule(p))  // star_expressions
 | ||||||
|         ) |         ) | ||||||
|         { |         { | ||||||
|             D(fprintf(stderr, "%*c+ invalid_legacy_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME expression_without_invalid")); |             D(fprintf(stderr, "%*c+ invalid_legacy_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME star_expressions")); | ||||||
|             _res = _PyPegen_check_legacy_stmt ( p , a ) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , b , "Missing parentheses in call to '%U'." , a -> v . Name . id ) : NULL; |             _res = _PyPegen_check_legacy_stmt ( p , a ) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , b , "Missing parentheses in call to '%U'. Did you mean %U(...)?" , a -> v . Name . id , a -> v . Name . id ) : NULL; | ||||||
|             if (_res == NULL && PyErr_Occurred()) { |             if (_res == NULL && PyErr_Occurred()) { | ||||||
|                 p->error_indicator = 1; |                 p->error_indicator = 1; | ||||||
|                 D(p->level--); |                 D(p->level--); | ||||||
|  | @ -18194,7 +18194,7 @@ invalid_legacy_expression_rule(Parser *p) | ||||||
|         } |         } | ||||||
|         p->mark = _mark; |         p->mark = _mark; | ||||||
|         D(fprintf(stderr, "%*c%s invalid_legacy_expression[%d-%d]: %s failed!\n", p->level, ' ', |         D(fprintf(stderr, "%*c%s invalid_legacy_expression[%d-%d]: %s failed!\n", p->level, ' ', | ||||||
|                   p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME expression_without_invalid")); |                   p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME star_expressions")); | ||||||
|     } |     } | ||||||
|     _res = NULL; |     _res = NULL; | ||||||
|   done: |   done: | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Pablo Galindo Salgado
						Pablo Galindo Salgado