Skip to content

Commit 68e3dca

Browse files
bpo-34013: Move the Python 2 hints from the exception constructor to the parser (GH-27392)
(cherry picked from commit ecc3c8e) Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
1 parent b977f85 commit 68e3dca

File tree

5 files changed

+28
-224
lines changed

5 files changed

+28
-224
lines changed

Grammar/python.gram

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -849,9 +849,10 @@ expression_without_invalid[expr_ty]:
849849
| disjunction
850850
| lambdef
851851
invalid_legacy_expression:
852-
| a=NAME b=expression_without_invalid {
853-
_PyPegen_check_legacy_stmt(p, a) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Missing parentheses in call to '%U'.", a->v.Name.id) : NULL}
854-
852+
| a=NAME b=star_expressions {
853+
_PyPegen_check_legacy_stmt(p, a) ? RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b,
854+
"Missing parentheses in call to '%U'. Did you mean %U(...)?", a->v.Name.id, a->v.Name.id) : NULL}
855+
855856
invalid_expression:
856857
| invalid_legacy_expression
857858
# !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"

Lib/test/test_exceptions.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,19 @@ def ckmsg(src, msg, exception=SyntaxError):
168168
self.fail("failed to get expected SyntaxError")
169169

170170
s = '''print "old style"'''
171-
ckmsg(s, "Missing parentheses in call to 'print'. "
172-
"Did you mean print(\"old style\")?")
171+
ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?")
173172

174173
s = '''print "old style",'''
175-
ckmsg(s, "Missing parentheses in call to 'print'. "
176-
"Did you mean print(\"old style\", end=\" \")?")
174+
ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?")
177175

178176
s = 'print f(a+b,c)'
179-
ckmsg(s, "Missing parentheses in call to 'print'.")
177+
ckmsg(s, "Missing parentheses in call to 'print'. Did you mean print(...)?")
180178

181179
s = '''exec "old style"'''
182-
ckmsg(s, "Missing parentheses in call to 'exec'")
180+
ckmsg(s, "Missing parentheses in call to 'exec'. Did you mean exec(...)?")
183181

184182
s = 'exec f(a+b,c)'
185-
ckmsg(s, "Missing parentheses in call to 'exec'.")
183+
ckmsg(s, "Missing parentheses in call to 'exec'. Did you mean exec(...)?")
186184

187185
# should not apply to subclasses, see issue #31161
188186
s = '''if True:\nprint "No indent"'''

Lib/test/test_print.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -140,21 +140,24 @@ def test_normal_string(self):
140140
with self.assertRaises(SyntaxError) as context:
141141
exec(python2_print_str)
142142

143-
self.assertIn('print("Hello World")', str(context.exception))
143+
self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)",
144+
str(context.exception))
144145

145146
def test_string_with_soft_space(self):
146147
python2_print_str = 'print "Hello World",'
147148
with self.assertRaises(SyntaxError) as context:
148149
exec(python2_print_str)
149150

150-
self.assertIn('print("Hello World", end=" ")', str(context.exception))
151+
self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)",
152+
str(context.exception))
151153

152154
def test_string_with_excessive_whitespace(self):
153155
python2_print_str = 'print "Hello World", '
154156
with self.assertRaises(SyntaxError) as context:
155157
exec(python2_print_str)
156158

157-
self.assertIn('print("Hello World", end=" ")', str(context.exception))
159+
self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)",
160+
str(context.exception))
158161

159162
def test_string_with_leading_whitespace(self):
160163
python2_print_str = '''if 1:
@@ -163,7 +166,8 @@ def test_string_with_leading_whitespace(self):
163166
with self.assertRaises(SyntaxError) as context:
164167
exec(python2_print_str)
165168

166-
self.assertIn('print("Hello World")', str(context.exception))
169+
self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)",
170+
str(context.exception))
167171

168172
# bpo-32685: Suggestions for print statement should be proper when
169173
# it is in the same line as the header of a compound statement
@@ -173,14 +177,16 @@ def test_string_with_semicolon(self):
173177
with self.assertRaises(SyntaxError) as context:
174178
exec(python2_print_str)
175179

176-
self.assertIn('print(p)', str(context.exception))
180+
self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)",
181+
str(context.exception))
177182

178183
def test_string_in_loop_on_same_line(self):
179184
python2_print_str = 'for i in s: print i'
180185
with self.assertRaises(SyntaxError) as context:
181186
exec(python2_print_str)
182187

183-
self.assertIn('print(i)', str(context.exception))
188+
self.assertIn("Missing parentheses in call to 'print'. Did you mean print(...)",
189+
str(context.exception))
184190

185191
def test_stream_redirection_hint_for_py2_migration(self):
186192
# Test correct hint produced for Py2 redirection syntax

Objects/exceptions.c

Lines changed: 0 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1475,9 +1475,6 @@ ComplexExtendsException(PyExc_Exception, AttributeError,
14751475
* SyntaxError extends Exception
14761476
*/
14771477

1478-
/* Helper function to customize error message for some syntax errors */
1479-
static int _report_missing_parentheses(PySyntaxErrorObject *self);
1480-
14811478
static int
14821479
SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds)
14831480
{
@@ -1520,18 +1517,6 @@ SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds)
15201517
PyErr_SetString(PyExc_TypeError, "end_offset must be provided when end_lineno is provided");
15211518
return -1;
15221519
}
1523-
1524-
/*
1525-
* Issue #21669: Custom error for 'print' & 'exec' as statements
1526-
*
1527-
* Only applies to SyntaxError instances, not to subclasses such
1528-
* as TabError or IndentationError (see issue #31161)
1529-
*/
1530-
if (Py_IS_TYPE(self, (PyTypeObject *)PyExc_SyntaxError) &&
1531-
self->text && PyUnicode_Check(self->text) &&
1532-
_report_missing_parentheses(self) < 0) {
1533-
return -1;
1534-
}
15351520
}
15361521
return 0;
15371522
}
@@ -3052,189 +3037,3 @@ _PyErr_TrySetFromCause(const char *format, ...)
30523037
PyErr_Restore(new_exc, new_val, new_tb);
30533038
return new_val;
30543039
}
3055-
3056-
3057-
/* To help with migration from Python 2, SyntaxError.__init__ applies some
3058-
* heuristics to try to report a more meaningful exception when print and
3059-
* exec are used like statements.
3060-
*
3061-
* The heuristics are currently expected to detect the following cases:
3062-
* - top level statement
3063-
* - statement in a nested suite
3064-
* - trailing section of a one line complex statement
3065-
*
3066-
* They're currently known not to trigger:
3067-
* - after a semi-colon
3068-
*
3069-
* The error message can be a bit odd in cases where the "arguments" are
3070-
* completely illegal syntactically, but that isn't worth the hassle of
3071-
* fixing.
3072-
*
3073-
* We also can't do anything about cases that are legal Python 3 syntax
3074-
* but mean something entirely different from what they did in Python 2
3075-
* (omitting the arguments entirely, printing items preceded by a unary plus
3076-
* or minus, using the stream redirection syntax).
3077-
*/
3078-
3079-
3080-
// Static helper for setting legacy print error message
3081-
static int
3082-
_set_legacy_print_statement_msg(PySyntaxErrorObject *self, Py_ssize_t start)
3083-
{
3084-
// PRINT_OFFSET is to remove the `print ` prefix from the data.
3085-
const int PRINT_OFFSET = 6;
3086-
const int STRIP_BOTH = 2;
3087-
Py_ssize_t start_pos = start + PRINT_OFFSET;
3088-
Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text);
3089-
Py_UCS4 semicolon = ';';
3090-
Py_ssize_t end_pos = PyUnicode_FindChar(self->text, semicolon,
3091-
start_pos, text_len, 1);
3092-
if (end_pos < -1) {
3093-
return -1;
3094-
} else if (end_pos == -1) {
3095-
end_pos = text_len;
3096-
}
3097-
3098-
PyObject *data = PyUnicode_Substring(self->text, start_pos, end_pos);
3099-
if (data == NULL) {
3100-
return -1;
3101-
}
3102-
3103-
PyObject *strip_sep_obj = PyUnicode_FromString(" \t\r\n");
3104-
if (strip_sep_obj == NULL) {
3105-
Py_DECREF(data);
3106-
return -1;
3107-
}
3108-
3109-
PyObject *new_data = _PyUnicode_XStrip(data, STRIP_BOTH, strip_sep_obj);
3110-
Py_DECREF(data);
3111-
Py_DECREF(strip_sep_obj);
3112-
if (new_data == NULL) {
3113-
return -1;
3114-
}
3115-
// gets the modified text_len after stripping `print `
3116-
text_len = PyUnicode_GET_LENGTH(new_data);
3117-
const char *maybe_end_arg = "";
3118-
if (text_len > 0 && PyUnicode_READ_CHAR(new_data, text_len-1) == ',') {
3119-
maybe_end_arg = " end=\" \"";
3120-
}
3121-
PyObject *error_msg = PyUnicode_FromFormat(
3122-
"Missing parentheses in call to 'print'. Did you mean print(%U%s)?",
3123-
new_data, maybe_end_arg
3124-
);
3125-
Py_DECREF(new_data);
3126-
if (error_msg == NULL)
3127-
return -1;
3128-
3129-
Py_XSETREF(self->msg, error_msg);
3130-
return 1;
3131-
}
3132-
3133-
static int
3134-
_check_for_legacy_statements(PySyntaxErrorObject *self, Py_ssize_t start)
3135-
{
3136-
/* Return values:
3137-
* -1: an error occurred
3138-
* 0: nothing happened
3139-
* 1: the check triggered & the error message was changed
3140-
*/
3141-
static PyObject *print_prefix = NULL;
3142-
static PyObject *exec_prefix = NULL;
3143-
Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text), match;
3144-
int kind = PyUnicode_KIND(self->text);
3145-
const void *data = PyUnicode_DATA(self->text);
3146-
3147-
/* Ignore leading whitespace */
3148-
while (start < text_len) {
3149-
Py_UCS4 ch = PyUnicode_READ(kind, data, start);
3150-
if (!Py_UNICODE_ISSPACE(ch))
3151-
break;
3152-
start++;
3153-
}
3154-
/* Checking against an empty or whitespace-only part of the string */
3155-
if (start == text_len) {
3156-
return 0;
3157-
}
3158-
3159-
/* Check for legacy print statements */
3160-
if (print_prefix == NULL) {
3161-
print_prefix = PyUnicode_InternFromString("print ");
3162-
if (print_prefix == NULL) {
3163-
return -1;
3164-
}
3165-
}
3166-
match = PyUnicode_Tailmatch(self->text, print_prefix,
3167-
start, text_len, -1);
3168-
if (match == -1) {
3169-
return -1;
3170-
}
3171-
if (match) {
3172-
return _set_legacy_print_statement_msg(self, start);
3173-
}
3174-
3175-
/* Check for legacy exec statements */
3176-
if (exec_prefix == NULL) {
3177-
exec_prefix = PyUnicode_InternFromString("exec ");
3178-
if (exec_prefix == NULL) {
3179-
return -1;
3180-
}
3181-
}
3182-
match = PyUnicode_Tailmatch(self->text, exec_prefix, start, text_len, -1);
3183-
if (match == -1) {
3184-
return -1;
3185-
}
3186-
if (match) {
3187-
PyObject *msg = PyUnicode_FromString("Missing parentheses in call "
3188-
"to 'exec'");
3189-
if (msg == NULL) {
3190-
return -1;
3191-
}
3192-
Py_XSETREF(self->msg, msg);
3193-
return 1;
3194-
}
3195-
/* Fall back to the default error message */
3196-
return 0;
3197-
}
3198-
3199-
static int
3200-
_report_missing_parentheses(PySyntaxErrorObject *self)
3201-
{
3202-
Py_UCS4 left_paren = 40;
3203-
Py_ssize_t left_paren_index;
3204-
Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text);
3205-
int legacy_check_result = 0;
3206-
3207-
/* Skip entirely if there is an opening parenthesis */
3208-
left_paren_index = PyUnicode_FindChar(self->text, left_paren,
3209-
0, text_len, 1);
3210-
if (left_paren_index < -1) {
3211-
return -1;
3212-
}
3213-
if (left_paren_index != -1) {
3214-
/* Use default error message for any line with an opening paren */
3215-
return 0;
3216-
}
3217-
/* Handle the simple statement case */
3218-
legacy_check_result = _check_for_legacy_statements(self, 0);
3219-
if (legacy_check_result < 0) {
3220-
return -1;
3221-
3222-
}
3223-
if (legacy_check_result == 0) {
3224-
/* Handle the one-line complex statement case */
3225-
Py_UCS4 colon = 58;
3226-
Py_ssize_t colon_index;
3227-
colon_index = PyUnicode_FindChar(self->text, colon,
3228-
0, text_len, 1);
3229-
if (colon_index < -1) {
3230-
return -1;
3231-
}
3232-
if (colon_index >= 0 && colon_index < text_len) {
3233-
/* Check again, starting from just after the colon */
3234-
if (_check_for_legacy_statements(self, colon_index+1) < 0) {
3235-
return -1;
3236-
}
3237-
}
3238-
}
3239-
return 0;
3240-
}

Parser/parser.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18180,7 +18180,7 @@ expression_without_invalid_rule(Parser *p)
1818018180
return _res;
1818118181
}
1818218182

18183-
// invalid_legacy_expression: NAME expression_without_invalid
18183+
// invalid_legacy_expression: NAME star_expressions
1818418184
static void *
1818518185
invalid_legacy_expression_rule(Parser *p)
1818618186
{
@@ -18191,22 +18191,22 @@ invalid_legacy_expression_rule(Parser *p)
1819118191
}
1819218192
void * _res = NULL;
1819318193
int _mark = p->mark;
18194-
{ // NAME expression_without_invalid
18194+
{ // NAME star_expressions
1819518195
if (p->error_indicator) {
1819618196
D(p->level--);
1819718197
return NULL;
1819818198
}
18199-
D(fprintf(stderr, "%*c> invalid_legacy_expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME expression_without_invalid"));
18199+
D(fprintf(stderr, "%*c> invalid_legacy_expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME star_expressions"));
1820018200
expr_ty a;
1820118201
expr_ty b;
1820218202
if (
1820318203
(a = _PyPegen_name_token(p)) // NAME
1820418204
&&
18205-
(b = expression_without_invalid_rule(p)) // expression_without_invalid
18205+
(b = star_expressions_rule(p)) // star_expressions
1820618206
)
1820718207
{
18208-
D(fprintf(stderr, "%*c+ invalid_legacy_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME expression_without_invalid"));
18209-
_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;
18208+
D(fprintf(stderr, "%*c+ invalid_legacy_expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME star_expressions"));
18209+
_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;
1821018210
if (_res == NULL && PyErr_Occurred()) {
1821118211
p->error_indicator = 1;
1821218212
D(p->level--);
@@ -18216,7 +18216,7 @@ invalid_legacy_expression_rule(Parser *p)
1821618216
}
1821718217
p->mark = _mark;
1821818218
D(fprintf(stderr, "%*c%s invalid_legacy_expression[%d-%d]: %s failed!\n", p->level, ' ',
18219-
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME expression_without_invalid"));
18219+
p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME star_expressions"));
1822018220
}
1822118221
_res = NULL;
1822218222
done:

0 commit comments

Comments
 (0)