bpo-43914: Highlight invalid ranges in SyntaxErrors (#25525)

To improve the user experience understanding what part of the error messages associated with SyntaxErrors is wrong, we can highlight the whole error range and not only place the caret at the first character. In this way:

>>> foo(x, z for z in range(10), t, w)
  File "<stdin>", line 1
    foo(x, z for z in range(10), t, w)
           ^
SyntaxError: Generator expression must be parenthesized

becomes

>>> foo(x, z for z in range(10), t, w)
  File "<stdin>", line 1
    foo(x, z for z in range(10), t, w)
           ^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
This commit is contained in:
Pablo Galindo 2021-04-23 14:27:05 +01:00 committed by GitHub
parent 91b69b77cf
commit a77aac4fca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 1687 additions and 1219 deletions

View file

@ -436,6 +436,18 @@ The following exceptions are the exceptions that are usually raised.
The source code text involved in the error.
.. attribute:: end_lineno
Which line number in the file the error occurred ends in. This is
1-indexed: the first line in the file has a ``lineno`` of 1.
.. attribute:: end_offset
The column in the end line where the error occurred finishes. This is
1-indexed: the first character in the line has an ``offset`` of 1.
.. versionchanged:: 3.10
Added the :attr:`end_lineno` and :attr:`end_offset` attributes.
.. exception:: IndentationError

View file

@ -171,6 +171,31 @@ These improvements are inspired by previous work in the PyPy interpreter.
(Contributed by Pablo Galindo in :issue:`42864` and Batuhan Taskaya in
:issue:`40176`.)
:exc:`SyntaxError` exceptions raised by the intepreter will now highlight the
full error range of the expression that consistutes the syntax error itself,
instead of just where the problem is detected. In this way, instead of displaying
(before Python 3.10):
.. code-block:: python
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^
SyntaxError: Generator expression must be parenthesized
now Python 3.10 will display the exception as:
.. code-block:: python
>>> foo(x, z for z in range(10), t, w)
File "<stdin>", line 1
foo(x, z for z in range(10), t, w)
^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized
This improvement has been contributed by Pablo Galindo in :issue:`43914`.
A considerable amount of new specialized messages for :exc:`SyntaxError` exceptions
have been incorporated. Some of the most notable ones:

View file

@ -779,32 +779,32 @@ t_atom[expr_ty]:
# From here on, there are rules for invalid syntax with specialised error messages
invalid_arguments:
| args ',' '*' { RAISE_SYNTAX_ERROR("iterable argument unpacking follows keyword argument unpacking") }
| a=expression for_if_clauses ',' [args | expression for_if_clauses] {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "Generator expression must be parenthesized") }
| a=args ',' '*' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable argument unpacking follows keyword argument unpacking") }
| a=expression b=for_if_clauses ',' [args | expression for_if_clauses] {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, PyPegen_last_item(b, comprehension_ty)->target, "Generator expression must be parenthesized") }
| a=args for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a) }
| args ',' a=expression for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "Generator expression must be parenthesized") }
| args ',' a=expression b=for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, asdl_seq_GET(b, b->size-1)->target, "Generator expression must be parenthesized") }
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }
invalid_kwarg:
| expression a='=' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
a, "expression cannot contain assignment, perhaps you meant \"==\"?") }
| a=expression b='=' {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(
a, b, "expression cannot contain assignment, perhaps you meant \"==\"?") }
invalid_expression:
# !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"
# Soft keywords need to also be ignored because they can be parsed as NAME NAME
| !(NAME STRING | SOFT_KEYWORD) a=disjunction expression {
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->end_col_offset - 1, "invalid syntax. Perhaps you forgot a comma?") }
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
invalid_named_expression:
| a=expression ':=' expression {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
a, "cannot use assignment expressions with %s", _PyPegen_get_expr_name(a)) }
| a=NAME b='=' bitwise_or !('='|':='|',') {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?") }
| a=NAME '=' b=bitwise_or !('='|':='|',') {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?") }
| !(list|tuple|genexp|'True'|'None'|'False') a=bitwise_or b='=' bitwise_or !('='|':='|',') {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(b, "cannot assign to %s here. Maybe you meant '==' instead of '='?",
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot assign to %s here. Maybe you meant '==' instead of '='?",
_PyPegen_get_expr_name(a)) }
invalid_assignment:
@ -841,25 +841,28 @@ invalid_primary:
invalid_comprehension:
| ('[' | '(' | '{') a=starred_expression for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable unpacking cannot be used in comprehension") }
| ('[' | '{') a=star_named_expression ',' [star_named_expressions] for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "did you forget parentheses around the comprehension target?") }
| ('[' | '{') a=star_named_expression ',' b=star_named_expressions for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, PyPegen_last_item(b, expr_ty),
"did you forget parentheses around the comprehension target?") }
| ('[' | '{') a=star_named_expression b=',' for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "did you forget parentheses around the comprehension target?") }
invalid_dict_comprehension:
| '{' a='**' bitwise_or for_if_clauses '}' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in dict comprehension") }
invalid_parameters:
| param_no_default* invalid_parameters_helper param_no_default {
RAISE_SYNTAX_ERROR("non-default argument follows default argument") }
| param_no_default* invalid_parameters_helper a=param_no_default {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "non-default argument follows default argument") }
invalid_parameters_helper: # This is only there to avoid type errors
| a=slash_with_default { _PyPegen_singleton_seq(p, a) }
| param_with_default+
invalid_lambda_parameters:
| lambda_param_no_default* invalid_lambda_parameters_helper lambda_param_no_default {
RAISE_SYNTAX_ERROR("non-default argument follows default argument") }
| lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "non-default argument follows default argument") }
invalid_lambda_parameters_helper:
| a=lambda_slash_with_default { _PyPegen_singleton_seq(p, a) }
| lambda_param_with_default+
invalid_star_etc:
| '*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR("named arguments must follow bare *") }
| a='*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "named arguments must follow bare *") }
| '*' ',' TYPE_COMMENT { RAISE_SYNTAX_ERROR("bare * has associated type comment") }
invalid_lambda_star_etc:
| '*' (':' | ',' (':' | '**')) { RAISE_SYNTAX_ERROR("named arguments must follow bare *") }
@ -897,7 +900,7 @@ invalid_try_stmt:
RAISE_INDENTATION_ERROR("expected an indented block after 'try' statement on line %d", a->lineno) }
invalid_except_stmt:
| 'except' a=expression ',' expressions ['as' NAME ] ':' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "exception group must be parenthesized") }
RAISE_SYNTAX_ERROR_STARTING_FROM(a, "exception group must be parenthesized") }
| a='except' expression ['as' NAME ] NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
| a='except' NEWLINE { RAISE_SYNTAX_ERROR("expected ':'") }
invalid_finally_stmt:
@ -942,10 +945,10 @@ invalid_class_def_raw:
invalid_double_starred_kvpairs:
| ','.double_starred_kvpair+ ',' invalid_kvpair
| expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use a starred expression in a dictionary value") }
| expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "cannot use a starred expression in a dictionary value") }
| expression a=':' &('}'|',') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") }
invalid_kvpair:
| a=expression !(':') {
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->end_col_offset - 1, "':' expected after dictionary key") }
| expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "cannot use a starred expression in a dictionary value") }
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->end_col_offset - 1, a->end_lineno, -1, "':' expected after dictionary key") }
| expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "cannot use a starred expression in a dictionary value") }
| expression a=':' {RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") }

View file

@ -20,6 +20,8 @@ typedef struct {
PyObject *filename;
PyObject *lineno;
PyObject *offset;
PyObject *end_lineno;
PyObject *end_offset;
PyObject *text;
PyObject *print_file_and_line;
} PySyntaxErrorObject;
@ -148,6 +150,13 @@ PyAPI_FUNC(void) PyErr_SyntaxLocationObject(
int lineno,
int col_offset);
PyAPI_FUNC(void) PyErr_RangedSyntaxLocationObject(
PyObject *filename,
int lineno,
int col_offset,
int end_lineno,
int end_col_offset);
PyAPI_FUNC(PyObject *) PyErr_ProgramTextObject(
PyObject *filename,
int lineno);

View file

@ -62,6 +62,8 @@ typedef struct _symtable_entry {
int ste_comp_iter_expr; /* non-zero if visiting a comprehension range expression */
int ste_lineno; /* first line of block */
int ste_col_offset; /* offset of first line of block */
int ste_end_lineno; /* end line of block */
int ste_end_col_offset; /* end offset of first line of block */
int ste_opt_lineno; /* lineno of last exec or import * */
int ste_opt_col_offset; /* offset of last exec or import * */
struct symtable *ste_table;

View file

@ -601,7 +601,7 @@ def test_syntaxerror_unindented_caret_position(self):
exitcode, stdout, stderr = assert_python_failure(script_name)
text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
# Confirm that the caret is located under the '=' sign
self.assertIn("\n 1 + 1 = 2\n ^\n", text)
self.assertIn("\n ^^^^^\n", text)
def test_syntaxerror_indented_caret_position(self):
script = textwrap.dedent("""\
@ -612,8 +612,8 @@ def test_syntaxerror_indented_caret_position(self):
script_name = _make_test_script(script_dir, 'script', script)
exitcode, stdout, stderr = assert_python_failure(script_name)
text = io.TextIOWrapper(io.BytesIO(stderr), 'ascii').read()
# Confirm that the caret is located under the first 1 character
self.assertIn("\n 1 + 1 = 2\n ^\n", text)
# Confirm that the caret starts under the first 1 character
self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text)
# Try the same with a form feed at the start of the indented line
script = (
@ -624,7 +624,7 @@ def test_syntaxerror_indented_caret_position(self):
exitcode, stdout, stderr = assert_python_failure(script_name)
text = io.TextIOWrapper(io.BytesIO(stderr), "ascii").read()
self.assertNotIn("\f", text)
self.assertIn("\n 1 + 1 = 2\n ^\n", text)
self.assertIn("\n 1 + 1 = 2\n ^^^^^\n", text)
def test_syntaxerror_multi_line_fstring(self):
script = 'foo = f"""{}\nfoo"""\n'
@ -650,7 +650,7 @@ def test_syntaxerror_invalid_escape_sequence_multi_line(self):
self.assertEqual(
stderr.splitlines()[-3:],
[ b' foo = """\\q"""',
b' ^',
b' ^^^^^^^^',
b'SyntaxError: invalid escape sequence \\q'
],
)

View file

@ -8,6 +8,7 @@
import pickle
import weakref
import errno
from textwrap import dedent
from test.support import (captured_stderr, check_impl_detail,
cpython_only, gc_collect,
@ -255,13 +256,13 @@ def baz():
check('from __future__ import doesnt_exist', 1, 1)
check('from __future__ import braces', 1, 1)
check('x=1\nfrom __future__ import division', 2, 1)
check('foo(1=2)', 1, 6)
check('foo(1=2)', 1, 5)
check('def f():\n x, y: int', 2, 3)
check('[*x for x in xs]', 1, 2)
check('foo(x for x in range(10), 100)', 1, 5)
check('for 1 in []: pass', 1, 5)
check('(yield i) = 2', 1, 11)
check('def f(*):\n pass', 1, 8)
check('(yield i) = 2', 1, 2)
check('def f(*):\n pass', 1, 7)
@cpython_only
def testSettingException(self):
@ -395,25 +396,31 @@ def testAttributes(self):
'filename' : 'filenameStr', 'filename2' : None}),
(SyntaxError, (), {'msg' : None, 'text' : None,
'filename' : None, 'lineno' : None, 'offset' : None,
'print_file_and_line' : None}),
'end_offset': None, 'print_file_and_line' : None}),
(SyntaxError, ('msgStr',),
{'args' : ('msgStr',), 'text' : None,
'print_file_and_line' : None, 'msg' : 'msgStr',
'filename' : None, 'lineno' : None, 'offset' : None}),
'filename' : None, 'lineno' : None, 'offset' : None,
'end_offset': None}),
(SyntaxError, ('msgStr', ('filenameStr', 'linenoStr', 'offsetStr',
'textStr')),
'textStr', 'endLinenoStr', 'endOffsetStr')),
{'offset' : 'offsetStr', 'text' : 'textStr',
'args' : ('msgStr', ('filenameStr', 'linenoStr',
'offsetStr', 'textStr')),
'offsetStr', 'textStr',
'endLinenoStr', 'endOffsetStr')),
'print_file_and_line' : None, 'msg' : 'msgStr',
'filename' : 'filenameStr', 'lineno' : 'linenoStr'}),
'filename' : 'filenameStr', 'lineno' : 'linenoStr',
'end_lineno': 'endLinenoStr', 'end_offset': 'endOffsetStr'}),
(SyntaxError, ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
'textStr', 'print_file_and_lineStr'),
'textStr', 'endLinenoStr', 'endOffsetStr',
'print_file_and_lineStr'),
{'text' : None,
'args' : ('msgStr', 'filenameStr', 'linenoStr', 'offsetStr',
'textStr', 'print_file_and_lineStr'),
'textStr', 'endLinenoStr', 'endOffsetStr',
'print_file_and_lineStr'),
'print_file_and_line' : None, 'msg' : 'msgStr',
'filename' : None, 'lineno' : None, 'offset' : None}),
'filename' : None, 'lineno' : None, 'offset' : None,
'end_lineno': None, 'end_offset': None}),
(UnicodeError, (), {'args' : (),}),
(UnicodeEncodeError, ('ascii', 'a', 0, 1,
'ordinal not in range'),
@ -459,7 +466,7 @@ def testAttributes(self):
e = exc(*args)
except:
print("\nexc=%r, args=%r" % (exc, args), file=sys.stderr)
raise
# raise
else:
# Verify module name
if not type(e).__name__.endswith('NaiveException'):
@ -1827,6 +1834,130 @@ def test_copy_pickle(self):
self.assertEqual(exc.name, orig.name)
self.assertEqual(exc.path, orig.path)
class SyntaxErrorTests(unittest.TestCase):
def test_range_of_offsets(self):
cases = [
# Basic range from 2->7
(("bad.py", 1, 2, "abcdefg", 1, 7),
dedent(
"""
File "bad.py", line 1
abcdefg
^^^^^
SyntaxError: bad bad
""")),
# end_offset = start_offset + 1
(("bad.py", 1, 2, "abcdefg", 1, 3),
dedent(
"""
File "bad.py", line 1
abcdefg
^
SyntaxError: bad bad
""")),
# Negative end offset
(("bad.py", 1, 2, "abcdefg", 1, -2),
dedent(
"""
File "bad.py", line 1
abcdefg
^
SyntaxError: bad bad
""")),
# end offset before starting offset
(("bad.py", 1, 4, "abcdefg", 1, 2),
dedent(
"""
File "bad.py", line 1
abcdefg
^
SyntaxError: bad bad
""")),
# Both offsets negative
(("bad.py", 1, -4, "abcdefg", 1, -2),
dedent(
"""
File "bad.py", line 1
abcdefg
SyntaxError: bad bad
""")),
# Both offsets negative and the end more negative
(("bad.py", 1, -4, "abcdefg", 1, -5),
dedent(
"""
File "bad.py", line 1
abcdefg
SyntaxError: bad bad
""")),
# Both offsets 0
(("bad.py", 1, 0, "abcdefg", 1, 0),
dedent(
"""
File "bad.py", line 1
abcdefg
SyntaxError: bad bad
""")),
# Start offset 0 and end offset not 0
(("bad.py", 1, 0, "abcdefg", 1, 5),
dedent(
"""
File "bad.py", line 1
abcdefg
SyntaxError: bad bad
""")),
# End offset pass the source lenght
(("bad.py", 1, 2, "abcdefg", 1, 100),
dedent(
"""
File "bad.py", line 1
abcdefg
^^^^^^
SyntaxError: bad bad
""")),
]
for args, expected in cases:
with self.subTest(args=args):
try:
raise SyntaxError("bad bad", args)
except SyntaxError as exc:
with support.captured_stderr() as err:
sys.__excepthook__(*sys.exc_info())
the_exception = exc
def test_attributes_new_constructor(self):
args = ("bad.py", 1, 2, "abcdefg", 1, 100)
the_exception = SyntaxError("bad bad", args)
filename, lineno, offset, error, end_lineno, end_offset = args
self.assertEqual(filename, the_exception.filename)
self.assertEqual(lineno, the_exception.lineno)
self.assertEqual(end_lineno, the_exception.end_lineno)
self.assertEqual(offset, the_exception.offset)
self.assertEqual(end_offset, the_exception.end_offset)
self.assertEqual(error, the_exception.text)
self.assertEqual("bad bad", the_exception.msg)
def test_attributes_old_constructor(self):
args = ("bad.py", 1, 2, "abcdefg")
the_exception = SyntaxError("bad bad", args)
filename, lineno, offset, error = args
self.assertEqual(filename, the_exception.filename)
self.assertEqual(lineno, the_exception.lineno)
self.assertEqual(None, the_exception.end_lineno)
self.assertEqual(offset, the_exception.offset)
self.assertEqual(None, the_exception.end_offset)
self.assertEqual(error, the_exception.text)
self.assertEqual("bad bad", the_exception.msg)
def test_incorrect_constructor(self):
args = ("bad.py", 1, 2)
self.assertRaises(TypeError, SyntaxError, "bad bad", args)
args = ("bad.py", 1, 2, 4, 5, 6, 7)
self.assertRaises(TypeError, SyntaxError, "bad bad", args)
args = ("bad.py", 1, 2, "abcdefg", 1)
self.assertRaises(TypeError, SyntaxError, "bad bad", args)
class PEP626Tests(unittest.TestCase):

View file

@ -1212,7 +1212,7 @@ def test_expression_with_assignment(self):
self._check_error(
"print(end1 + end2 = ' ')",
'expression cannot contain assignment, perhaps you meant "=="?',
offset=19
offset=7
)
def test_curly_brace_after_primary_raises_immediately(self):

View file

@ -0,0 +1,3 @@
:exc:`SyntaxError` exceptions raised by the intepreter will highlight the
full error range of the expression that consistutes the syntax error itself,
instead of just where the problem is detected. Patch by Pablo Galindo.

View file

@ -1494,30 +1494,33 @@ SyntaxError_init(PySyntaxErrorObject *self, PyObject *args, PyObject *kwds)
if (lenargs == 2) {
info = PyTuple_GET_ITEM(args, 1);
info = PySequence_Tuple(info);
if (!info)
return -1;
if (PyTuple_GET_SIZE(info) != 4) {
/* not a very good error message, but it's what Python 2.4 gives */
PyErr_SetString(PyExc_IndexError, "tuple index out of range");
Py_DECREF(info);
if (!info) {
return -1;
}
Py_INCREF(PyTuple_GET_ITEM(info, 0));
Py_XSETREF(self->filename, PyTuple_GET_ITEM(info, 0));
Py_INCREF(PyTuple_GET_ITEM(info, 1));
Py_XSETREF(self->lineno, PyTuple_GET_ITEM(info, 1));
Py_INCREF(PyTuple_GET_ITEM(info, 2));
Py_XSETREF(self->offset, PyTuple_GET_ITEM(info, 2));
Py_INCREF(PyTuple_GET_ITEM(info, 3));
Py_XSETREF(self->text, PyTuple_GET_ITEM(info, 3));
self->end_lineno = NULL;
self->end_offset = NULL;
if (!PyArg_ParseTuple(info, "OOOO|OO",
&self->filename, &self->lineno,
&self->offset, &self->text,
&self->end_lineno, &self->end_offset)) {
Py_DECREF(info);
return -1;
}
Py_INCREF(self->filename);
Py_INCREF(self->lineno);
Py_INCREF(self->offset);
Py_INCREF(self->text);
Py_XINCREF(self->end_lineno);
Py_XINCREF(self->end_offset);
Py_DECREF(info);
if (self->end_lineno != NULL && self->end_offset == NULL) {
PyErr_SetString(PyExc_TypeError, "end_offset must be provided when end_lineno is provided");
return -1;
}
/*
* Issue #21669: Custom error for 'print' & 'exec' as statements
*
@ -1540,6 +1543,8 @@ SyntaxError_clear(PySyntaxErrorObject *self)
Py_CLEAR(self->filename);
Py_CLEAR(self->lineno);
Py_CLEAR(self->offset);
Py_CLEAR(self->end_lineno);
Py_CLEAR(self->end_offset);
Py_CLEAR(self->text);
Py_CLEAR(self->print_file_and_line);
return BaseException_clear((PyBaseExceptionObject *)self);
@ -1560,6 +1565,8 @@ SyntaxError_traverse(PySyntaxErrorObject *self, visitproc visit, void *arg)
Py_VISIT(self->filename);
Py_VISIT(self->lineno);
Py_VISIT(self->offset);
Py_VISIT(self->end_lineno);
Py_VISIT(self->end_offset);
Py_VISIT(self->text);
Py_VISIT(self->print_file_and_line);
return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg);
@ -1650,6 +1657,10 @@ static PyMemberDef SyntaxError_members[] = {
PyDoc_STR("exception offset")},
{"text", T_OBJECT, offsetof(PySyntaxErrorObject, text), 0,
PyDoc_STR("exception text")},
{"end_lineno", T_OBJECT, offsetof(PySyntaxErrorObject, end_lineno), 0,
PyDoc_STR("exception end lineno")},
{"end_offset", T_OBJECT, offsetof(PySyntaxErrorObject, end_offset), 0,
PyDoc_STR("exception end offset")},
{"print_file_and_line", T_OBJECT,
offsetof(PySyntaxErrorObject, print_file_and_line), 0,
PyDoc_STR("exception print_file_and_line")},

File diff suppressed because it is too large Load diff

View file

@ -274,7 +274,7 @@ raise_unclosed_parentheses_error(Parser *p) {
int error_lineno = p->tok->parenlinenostack[p->tok->level-1];
int error_col = p->tok->parencolstack[p->tok->level-1];
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError,
error_lineno, error_col,
error_lineno, error_col, error_lineno, -1,
"'%c' was never closed",
p->tok->parenstack[p->tok->level-1]);
}
@ -366,7 +366,7 @@ tokenizer_error(Parser *p)
msg = "unknown parsing error";
}
RAISE_ERROR_KNOWN_LOCATION(p, errtype, p->tok->lineno, col_offset, msg);
RAISE_ERROR_KNOWN_LOCATION(p, errtype, p->tok->lineno, col_offset, p->tok->lineno, -1, msg);
return -1;
}
@ -375,6 +375,7 @@ _PyPegen_raise_error(Parser *p, PyObject *errtype, const char *errmsg, ...)
{
Token *t = p->known_err_token != NULL ? p->known_err_token : p->tokens[p->fill - 1];
Py_ssize_t col_offset;
Py_ssize_t end_col_offset = -1;
if (t->col_offset == -1) {
col_offset = Py_SAFE_DOWNCAST(p->tok->cur - p->tok->buf,
intptr_t, int);
@ -382,10 +383,13 @@ _PyPegen_raise_error(Parser *p, PyObject *errtype, const char *errmsg, ...)
col_offset = t->col_offset + 1;
}
if (t->end_col_offset != -1) {
end_col_offset = t->end_col_offset + 1;
}
va_list va;
va_start(va, errmsg);
_PyPegen_raise_error_known_location(p, errtype, t->lineno,
col_offset, errmsg, va);
_PyPegen_raise_error_known_location(p, errtype, t->lineno, col_offset, t->end_lineno, end_col_offset, errmsg, va);
va_end(va);
return NULL;
@ -416,6 +420,7 @@ get_error_line(Parser *p, Py_ssize_t lineno)
void *
_PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
Py_ssize_t lineno, Py_ssize_t col_offset,
Py_ssize_t end_lineno, Py_ssize_t end_col_offset,
const char *errmsg, va_list va)
{
PyObject *value = NULL;
@ -424,6 +429,13 @@ _PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
PyObject *tmp = NULL;
p->error_indicator = 1;
if (end_lineno == CURRENT_POS) {
end_lineno = p->tok->lineno;
}
if (end_col_offset == CURRENT_POS) {
end_col_offset = p->tok->cur - p->tok->line_start;
}
if (p->start_rule == Py_fstring_input) {
const char *fstring_msg = "f-string: ";
Py_ssize_t len = strlen(fstring_msg) + strlen(errmsg);
@ -475,14 +487,19 @@ _PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
if (p->start_rule == Py_fstring_input) {
col_offset -= p->starting_col_offset;
end_col_offset -= p->starting_col_offset;
}
Py_ssize_t col_number = col_offset;
Py_ssize_t end_col_number = end_col_offset;
if (p->tok->encoding != NULL) {
col_number = byte_offset_to_character_offset(error_line, col_offset);
end_col_number = end_col_number > 0 ?
byte_offset_to_character_offset(error_line, end_col_offset) :
end_col_number;
}
tmp = Py_BuildValue("(OiiN)", p->tok->filename, lineno, col_number, error_line);
tmp = Py_BuildValue("(OiiNii)", p->tok->filename, lineno, col_number, error_line, end_lineno, end_col_number);
if (!tmp) {
goto error;
}
@ -1494,6 +1511,13 @@ _PyPegen_seq_flatten(Parser *p, asdl_seq *seqs)
return flattened_seq;
}
void *
_PyPegen_seq_last_item(asdl_seq *seq)
{
Py_ssize_t len = asdl_seq_LEN(seq);
return asdl_seq_GET_UNTYPED(seq, len - 1);
}
/* Creates a new name of the form <first_name>.<second_name> */
expr_ty
_PyPegen_join_names_with_dot(Parser *p, expr_ty first_name, expr_ty second_name)
@ -2398,7 +2422,7 @@ _PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args)
return NULL;
}
return RAISE_SYNTAX_ERROR_KNOWN_LOCATION(
return RAISE_SYNTAX_ERROR_STARTING_FROM(
(expr_ty) asdl_seq_GET(args->v.Call.args, len - 1),
"Generator expression must be parenthesized"
);

View file

@ -136,30 +136,41 @@ const char *_PyPegen_get_expr_name(expr_ty);
void *_PyPegen_raise_error(Parser *p, PyObject *errtype, const char *errmsg, ...);
void *_PyPegen_raise_error_known_location(Parser *p, PyObject *errtype,
Py_ssize_t lineno, Py_ssize_t col_offset,
Py_ssize_t end_lineno, Py_ssize_t end_col_offset,
const char *errmsg, va_list va);
void *_PyPegen_dummy_name(Parser *p, ...);
void * _PyPegen_seq_last_item(asdl_seq *seq);
#define PyPegen_last_item(seq, type) ((type)_PyPegen_seq_last_item((asdl_seq*)seq))
#define CURRENT_POS (-5)
Py_LOCAL_INLINE(void *)
RAISE_ERROR_KNOWN_LOCATION(Parser *p, PyObject *errtype,
Py_ssize_t lineno, Py_ssize_t col_offset,
Py_ssize_t end_lineno, Py_ssize_t end_col_offset,
const char *errmsg, ...)
{
va_list va;
va_start(va, errmsg);
_PyPegen_raise_error_known_location(p, errtype, lineno, col_offset + 1,
errmsg, va);
Py_ssize_t _col_offset = (col_offset == CURRENT_POS ? CURRENT_POS : col_offset + 1);
Py_ssize_t _end_col_offset = (end_col_offset == CURRENT_POS ? CURRENT_POS : end_col_offset + 1);
_PyPegen_raise_error_known_location(p, errtype, lineno, _col_offset, end_lineno, _end_col_offset, errmsg, va);
va_end(va);
return NULL;
}
#define UNUSED(expr) do { (void)(expr); } while (0)
#define EXTRA_EXPR(head, tail) head->lineno, (head)->col_offset, (tail)->end_lineno, (tail)->end_col_offset, p->arena
#define EXTRA _start_lineno, _start_col_offset, _end_lineno, _end_col_offset, p->arena
#define RAISE_SYNTAX_ERROR(msg, ...) _PyPegen_raise_error(p, PyExc_SyntaxError, msg, ##__VA_ARGS__)
#define RAISE_INDENTATION_ERROR(msg, ...) _PyPegen_raise_error(p, PyExc_IndentationError, msg, ##__VA_ARGS__)
#define RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, msg, ...) \
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, (a)->lineno, (a)->col_offset, (b)->end_lineno, (b)->end_col_offset, msg, ##__VA_ARGS__)
#define RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, msg, ...) \
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, (a)->lineno, (a)->col_offset, msg, ##__VA_ARGS__)
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, (a)->lineno, (a)->col_offset, (a)->end_lineno, (a)->end_col_offset, msg, ##__VA_ARGS__)
#define RAISE_SYNTAX_ERROR_STARTING_FROM(a, msg, ...) \
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, (a)->lineno, (a)->col_offset, CURRENT_POS, CURRENT_POS, msg, ##__VA_ARGS__)
Py_LOCAL_INLINE(void *)
CHECK_CALL(Parser *p, void *result)

View file

@ -197,6 +197,8 @@ struct compiler_unit {
int u_firstlineno; /* the first lineno of the block */
int u_lineno; /* the lineno for the current stmt */
int u_col_offset; /* the offset of the current stmt */
int u_end_lineno; /* the end line of the current stmt */
int u_end_col_offset; /* the end offset of the current stmt */
};
/* This struct captures the global state of a compilation.
@ -641,6 +643,8 @@ compiler_enter_scope(struct compiler *c, identifier name,
u->u_firstlineno = lineno;
u->u_lineno = 0;
u->u_col_offset = 0;
u->u_end_lineno = 0;
u->u_end_col_offset = 0;
u->u_consts = PyDict_New();
if (!u->u_consts) {
compiler_unit_free(u);
@ -911,7 +915,9 @@ compiler_next_instr(basicblock *b)
#define SET_LOC(c, x) \
(c)->u->u_lineno = (x)->lineno; \
(c)->u->u_col_offset = (x)->col_offset;
(c)->u->u_col_offset = (x)->col_offset; \
(c)->u->u_end_lineno = (x)->end_lineno; \
(c)->u->u_end_col_offset = (x)->end_col_offset;
/* Return the stack effect of opcode with argument oparg.
@ -5474,8 +5480,9 @@ compiler_error(struct compiler *c, const char *format, ...)
Py_INCREF(Py_None);
loc = Py_None;
}
PyObject *args = Py_BuildValue("O(OiiO)", msg, c->c_filename,
c->u->u_lineno, c->u->u_col_offset + 1, loc);
PyObject *args = Py_BuildValue("O(OiiOii)", msg, c->c_filename,
c->u->u_lineno, c->u->u_col_offset + 1, loc,
c->u->u_end_lineno, c->u->u_end_col_offset + 1);
Py_DECREF(msg);
if (args == NULL) {
goto exit;

View file

@ -1545,14 +1545,17 @@ PyErr_SyntaxLocation(const char *filename, int lineno)
If the exception is not a SyntaxError, also sets additional attributes
to make printing of exceptions believe it is a syntax error. */
void
PyErr_SyntaxLocationObject(PyObject *filename, int lineno, int col_offset)
static void
PyErr_SyntaxLocationObjectEx(PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset)
{
PyObject *exc, *v, *tb, *tmp;
_Py_IDENTIFIER(filename);
_Py_IDENTIFIER(lineno);
_Py_IDENTIFIER(end_lineno);
_Py_IDENTIFIER(msg);
_Py_IDENTIFIER(offset);
_Py_IDENTIFIER(end_offset);
_Py_IDENTIFIER(print_file_and_line);
_Py_IDENTIFIER(text);
PyThreadState *tstate = _PyThreadState_GET();
@ -1582,6 +1585,32 @@ PyErr_SyntaxLocationObject(PyObject *filename, int lineno, int col_offset)
_PyErr_Clear(tstate);
}
Py_XDECREF(tmp);
tmp = NULL;
if (end_lineno >= 0) {
tmp = PyLong_FromLong(end_lineno);
if (tmp == NULL) {
_PyErr_Clear(tstate);
}
}
if (_PyObject_SetAttrId(v, &PyId_end_lineno, tmp ? tmp : Py_None)) {
_PyErr_Clear(tstate);
}
Py_XDECREF(tmp);
tmp = NULL;
if (end_col_offset >= 0) {
tmp = PyLong_FromLong(end_col_offset);
if (tmp == NULL) {
_PyErr_Clear(tstate);
}
}
if (_PyObject_SetAttrId(v, &PyId_end_offset, tmp ? tmp : Py_None)) {
_PyErr_Clear(tstate);
}
Py_XDECREF(tmp);
tmp = NULL;
if (filename != NULL) {
if (_PyObject_SetAttrId(v, &PyId_filename, filename)) {
_PyErr_Clear(tstate);
@ -1633,6 +1662,17 @@ PyErr_SyntaxLocationObject(PyObject *filename, int lineno, int col_offset)
_PyErr_Restore(tstate, exc, v, tb);
}
void
PyErr_SyntaxLocationObject(PyObject *filename, int lineno, int col_offset) {
PyErr_SyntaxLocationObjectEx(filename, lineno, col_offset, lineno, -1);
}
void
PyErr_RangedSyntaxLocationObject(PyObject *filename, int lineno, int col_offset,
int end_lineno, int end_col_offset) {
PyErr_SyntaxLocationObjectEx(filename, lineno, col_offset, end_lineno, end_col_offset);
}
void
PyErr_SyntaxLocationEx(const char *filename, int lineno, int col_offset)
{

View file

@ -510,7 +510,9 @@ PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
static int
parse_syntax_error(PyObject *err, PyObject **message, PyObject **filename,
Py_ssize_t *lineno, Py_ssize_t *offset, PyObject **text)
Py_ssize_t *lineno, Py_ssize_t *offset,
Py_ssize_t* end_lineno, Py_ssize_t* end_offset,
PyObject **text)
{
Py_ssize_t hold;
PyObject *v;
@ -518,6 +520,8 @@ parse_syntax_error(PyObject *err, PyObject **message, PyObject **filename,
_Py_IDENTIFIER(filename);
_Py_IDENTIFIER(lineno);
_Py_IDENTIFIER(offset);
_Py_IDENTIFIER(end_lineno);
_Py_IDENTIFIER(end_offset);
_Py_IDENTIFIER(text);
*message = NULL;
@ -565,6 +569,44 @@ parse_syntax_error(PyObject *err, PyObject **message, PyObject **filename,
*offset = hold;
}
if (Py_TYPE(err) == (PyTypeObject*)PyExc_SyntaxError) {
v = _PyObject_GetAttrId(err, &PyId_end_lineno);
if (!v) {
PyErr_Clear();
*end_lineno = *lineno;
}
else if (v == Py_None) {
*end_lineno = *lineno;
Py_DECREF(v);
} else {
hold = PyLong_AsSsize_t(v);
Py_DECREF(v);
if (hold < 0 && PyErr_Occurred())
goto finally;
*end_lineno = hold;
}
v = _PyObject_GetAttrId(err, &PyId_end_offset);
if (!v) {
PyErr_Clear();
*end_offset = -1;
}
else if (v == Py_None) {
*end_offset = -1;
Py_DECREF(v);
} else {
hold = PyLong_AsSsize_t(v);
Py_DECREF(v);
if (hold < 0 && PyErr_Occurred())
goto finally;
*end_offset = hold;
}
} else {
// SyntaxError subclasses
*end_lineno = *lineno;
*end_offset = -1;
}
v = _PyObject_GetAttrId(err, &PyId_text);
if (!v)
goto finally;
@ -584,8 +626,9 @@ parse_syntax_error(PyObject *err, PyObject **message, PyObject **filename,
}
static void
print_error_text(PyObject *f, Py_ssize_t offset, PyObject *text_obj)
print_error_text(PyObject *f, Py_ssize_t offset, Py_ssize_t end_offset, PyObject *text_obj)
{
size_t caret_repetitions = (end_offset > 0 && end_offset > offset) ? end_offset - offset : 1;
/* Convert text to a char pointer; return if error */
const char *text = PyUnicode_AsUTF8(text_obj);
if (text == NULL)
@ -645,7 +688,10 @@ print_error_text(PyObject *f, Py_ssize_t offset, PyObject *text_obj)
while (--offset >= 0) {
PyFile_WriteString(" ", f);
}
PyFile_WriteString("^\n", f);
for (size_t caret_iter=0; caret_iter < caret_repetitions ; caret_iter++) {
PyFile_WriteString("^", f);
}
PyFile_WriteString("\n", f);
}
@ -865,11 +911,12 @@ print_exception(PyObject *f, PyObject *value)
(err = _PyObject_LookupAttrId(value, &PyId_print_file_and_line, &tmp)) > 0)
{
PyObject *message, *filename, *text;
Py_ssize_t lineno, offset;
Py_ssize_t lineno, offset, end_lineno, end_offset;
err = 0;
Py_DECREF(tmp);
if (!parse_syntax_error(value, &message, &filename,
&lineno, &offset, &text))
&lineno, &offset,
&end_lineno, &end_offset, &text))
PyErr_Clear();
else {
PyObject *line;
@ -886,7 +933,21 @@ print_exception(PyObject *f, PyObject *value)
}
if (text != NULL) {
print_error_text(f, offset, text);
Py_ssize_t line_size;
const char* error_line = PyUnicode_AsUTF8AndSize(text, &line_size);
// If the location of the error spawn multiple lines, we want
// to just print the first one and highlight everything until
// the end of that one since we don't support multi-line error
// messages.
if (end_lineno > lineno) {
end_offset = (error_line != NULL) ? line_size : -1;
}
// Limit the ammount of '^' that we can display to
// the size of the text in the source line.
if (error_line != NULL && end_offset > line_size + 1) {
end_offset = line_size + 1;
}
print_error_text(f, offset, end_offset, text);
Py_DECREF(text);
}

View file

@ -47,7 +47,8 @@
static PySTEntryObject *
ste_new(struct symtable *st, identifier name, _Py_block_ty block,
void *key, int lineno, int col_offset)
void *key, int lineno, int col_offset,
int end_lineno, int end_col_offset)
{
PySTEntryObject *ste = NULL;
PyObject *k = NULL;
@ -81,6 +82,8 @@ ste_new(struct symtable *st, identifier name, _Py_block_ty block,
ste->ste_opt_col_offset = 0;
ste->ste_lineno = lineno;
ste->ste_col_offset = col_offset;
ste->ste_end_lineno = end_lineno;
ste->ste_end_col_offset = end_col_offset;
if (st->st_cur != NULL &&
(st->st_cur->ste_nested ||
@ -190,8 +193,9 @@ PyTypeObject PySTEntry_Type = {
static int symtable_analyze(struct symtable *st);
static int symtable_enter_block(struct symtable *st, identifier name,
_Py_block_ty block, void *ast, int lineno,
int col_offset);
_Py_block_ty block, void *ast,
int lineno, int col_offset,
int end_lineno, int end_col_offset);
static int symtable_exit_block(struct symtable *st);
static int symtable_visit_stmt(struct symtable *st, stmt_ty s);
static int symtable_visit_expr(struct symtable *st, expr_ty s);
@ -296,7 +300,7 @@ _PySymtable_Build(mod_ty mod, PyObject *filename, PyFutureFeatures *future)
/* Make the initial symbol information gathering pass */
if (!GET_IDENTIFIER(top) ||
!symtable_enter_block(st, top, ModuleBlock, (void *)mod, 0, 0)) {
!symtable_enter_block(st, top, ModuleBlock, (void *)mod, 0, 0, 0, 0)) {
_PySymtable_Free(st);
return NULL;
}
@ -409,9 +413,11 @@ error_at_directive(PySTEntryObject *ste, PyObject *name)
assert(PyTuple_CheckExact(data));
assert(PyUnicode_CheckExact(PyTuple_GET_ITEM(data, 0)));
if (PyUnicode_Compare(PyTuple_GET_ITEM(data, 0), name) == 0) {
PyErr_SyntaxLocationObject(ste->ste_table->st_filename,
PyLong_AsLong(PyTuple_GET_ITEM(data, 1)),
PyLong_AsLong(PyTuple_GET_ITEM(data, 2)) + 1);
PyErr_RangedSyntaxLocationObject(ste->ste_table->st_filename,
PyLong_AsLong(PyTuple_GET_ITEM(data, 1)),
PyLong_AsLong(PyTuple_GET_ITEM(data, 2)) + 1,
PyLong_AsLong(PyTuple_GET_ITEM(data, 3)),
PyLong_AsLong(PyTuple_GET_ITEM(data, 4)) + 1);
return 0;
}
@ -958,11 +964,12 @@ symtable_exit_block(struct symtable *st)
static int
symtable_enter_block(struct symtable *st, identifier name, _Py_block_ty block,
void *ast, int lineno, int col_offset)
void *ast, int lineno, int col_offset,
int end_lineno, int end_col_offset)
{
PySTEntryObject *prev = NULL, *ste;
ste = ste_new(st, name, block, ast, lineno, col_offset);
ste = ste_new(st, name, block, ast, lineno, col_offset, end_lineno, end_col_offset);
if (ste == NULL)
return 0;
if (PyList_Append(st->st_stack, (PyObject *)ste) < 0) {
@ -1018,9 +1025,11 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s
if ((flag & DEF_PARAM) && (val & DEF_PARAM)) {
/* Is it better to use 'mangled' or 'name' here? */
PyErr_Format(PyExc_SyntaxError, DUPLICATE_ARGUMENT, name);
PyErr_SyntaxLocationObject(st->st_filename,
ste->ste_lineno,
ste->ste_col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
ste->ste_lineno,
ste->ste_col_offset + 1,
ste->ste_end_lineno,
ste->ste_end_col_offset + 1);
goto error;
}
val |= flag;
@ -1040,9 +1049,11 @@ symtable_add_def_helper(struct symtable *st, PyObject *name, int flag, struct _s
if (val & (DEF_GLOBAL | DEF_NONLOCAL)) {
PyErr_Format(PyExc_SyntaxError,
NAMED_EXPR_COMP_INNER_LOOP_CONFLICT, name);
PyErr_SyntaxLocationObject(st->st_filename,
ste->ste_lineno,
ste->ste_col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
ste->ste_lineno,
ste->ste_col_offset + 1,
ste->ste_end_lineno,
ste->ste_end_col_offset + 1);
goto error;
}
val |= DEF_COMP_ITER;
@ -1141,7 +1152,8 @@ symtable_add_def(struct symtable *st, PyObject *name, int flag) {
}
static int
symtable_record_directive(struct symtable *st, identifier name, int lineno, int col_offset)
symtable_record_directive(struct symtable *st, identifier name, int lineno,
int col_offset, int end_lineno, int end_col_offset)
{
PyObject *data, *mangled;
int res;
@ -1153,7 +1165,7 @@ symtable_record_directive(struct symtable *st, identifier name, int lineno, int
mangled = _Py_Mangle(st->st_private, name);
if (!mangled)
return 0;
data = Py_BuildValue("(Nii)", mangled, lineno, col_offset);
data = Py_BuildValue("(Niiii)", mangled, lineno, col_offset, end_lineno, end_col_offset);
if (!data)
return 0;
res = PyList_Append(st->st_cur->ste_directives, data);
@ -1184,8 +1196,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
if (s->v.FunctionDef.decorator_list)
VISIT_SEQ(st, expr, s->v.FunctionDef.decorator_list);
if (!symtable_enter_block(st, s->v.FunctionDef.name,
FunctionBlock, (void *)s, s->lineno,
s->col_offset))
FunctionBlock, (void *)s,
s->lineno, s->col_offset,
s->end_lineno, s->end_col_offset))
VISIT_QUIT(st, 0);
VISIT(st, arguments, s->v.FunctionDef.args);
VISIT_SEQ(st, stmt, s->v.FunctionDef.body);
@ -1201,7 +1214,8 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
if (s->v.ClassDef.decorator_list)
VISIT_SEQ(st, expr, s->v.ClassDef.decorator_list);
if (!symtable_enter_block(st, s->v.ClassDef.name, ClassBlock,
(void *)s, s->lineno, s->col_offset))
(void *)s, s->lineno, s->col_offset,
s->end_lineno, s->end_col_offset))
VISIT_QUIT(st, 0);
tmp = st->st_private;
st->st_private = s->v.ClassDef.name;
@ -1237,9 +1251,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
PyErr_Format(PyExc_SyntaxError,
cur & DEF_GLOBAL ? GLOBAL_ANNOT : NONLOCAL_ANNOT,
e_name->v.Name.id);
PyErr_SyntaxLocationObject(st->st_filename,
s->lineno,
s->col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
s->lineno,
s->col_offset + 1,
s->end_lineno,
s->end_col_offset + 1);
VISIT_QUIT(st, 0);
}
if (s->v.AnnAssign.simple &&
@ -1336,14 +1352,17 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
}
PyErr_Format(PyExc_SyntaxError,
msg, name);
PyErr_SyntaxLocationObject(st->st_filename,
s->lineno,
s->col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
s->lineno,
s->col_offset + 1,
s->end_lineno,
s->end_col_offset + 1);
VISIT_QUIT(st, 0);
}
if (!symtable_add_def(st, name, DEF_GLOBAL))
VISIT_QUIT(st, 0);
if (!symtable_record_directive(st, name, s->lineno, s->col_offset))
if (!symtable_record_directive(st, name, s->lineno, s->col_offset,
s->end_lineno, s->end_col_offset))
VISIT_QUIT(st, 0);
}
break;
@ -1368,14 +1387,17 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
msg = NONLOCAL_AFTER_ASSIGN;
}
PyErr_Format(PyExc_SyntaxError, msg, name);
PyErr_SyntaxLocationObject(st->st_filename,
s->lineno,
s->col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
s->lineno,
s->col_offset + 1,
s->end_lineno,
s->end_col_offset + 1);
VISIT_QUIT(st, 0);
}
if (!symtable_add_def(st, name, DEF_NONLOCAL))
VISIT_QUIT(st, 0);
if (!symtable_record_directive(st, name, s->lineno, s->col_offset))
if (!symtable_record_directive(st, name, s->lineno, s->col_offset,
s->end_lineno, s->end_col_offset))
VISIT_QUIT(st, 0);
}
break;
@ -1406,8 +1428,9 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s)
if (s->v.AsyncFunctionDef.decorator_list)
VISIT_SEQ(st, expr, s->v.AsyncFunctionDef.decorator_list);
if (!symtable_enter_block(st, s->v.AsyncFunctionDef.name,
FunctionBlock, (void *)s, s->lineno,
s->col_offset))
FunctionBlock, (void *)s,
s->lineno, s->col_offset,
s->end_lineno, s->end_col_offset))
VISIT_QUIT(st, 0);
st->st_cur->ste_coroutine = 1;
VISIT(st, arguments, s->v.AsyncFunctionDef.args);
@ -1453,9 +1476,11 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
long target_in_scope = _PyST_GetSymbol(ste, target_name);
if (target_in_scope & DEF_COMP_ITER) {
PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_CONFLICT, target_name);
PyErr_SyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset);
PyErr_RangedSyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset + 1,
e->end_lineno,
e->end_col_offset + 1);
VISIT_QUIT(st, 0);
}
continue;
@ -1471,7 +1496,8 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
if (!symtable_add_def(st, target_name, DEF_NONLOCAL))
VISIT_QUIT(st, 0);
}
if (!symtable_record_directive(st, target_name, e->lineno, e->col_offset))
if (!symtable_record_directive(st, target_name, e->lineno, e->col_offset,
e->end_lineno, e->end_col_offset))
VISIT_QUIT(st, 0);
return symtable_add_def_helper(st, target_name, DEF_LOCAL, ste);
@ -1480,7 +1506,8 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
if (ste->ste_type == ModuleBlock) {
if (!symtable_add_def(st, target_name, DEF_GLOBAL))
VISIT_QUIT(st, 0);
if (!symtable_record_directive(st, target_name, e->lineno, e->col_offset))
if (!symtable_record_directive(st, target_name, e->lineno, e->col_offset,
e->end_lineno, e->end_col_offset))
VISIT_QUIT(st, 0);
return symtable_add_def_helper(st, target_name, DEF_GLOBAL, ste);
@ -1488,9 +1515,11 @@ symtable_extend_namedexpr_scope(struct symtable *st, expr_ty e)
/* Disallow usage in ClassBlock */
if (ste->ste_type == ClassBlock) {
PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_IN_CLASS);
PyErr_SyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset);
PyErr_RangedSyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset + 1,
e->end_lineno,
e->end_col_offset + 1);
VISIT_QUIT(st, 0);
}
}
@ -1508,9 +1537,11 @@ symtable_handle_namedexpr(struct symtable *st, expr_ty e)
if (st->st_cur->ste_comp_iter_expr > 0) {
/* Assignment isn't allowed in a comprehension iterable expression */
PyErr_Format(PyExc_SyntaxError, NAMED_EXPR_COMP_ITER_EXPR);
PyErr_SyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset);
PyErr_RangedSyntaxLocationObject(st->st_filename,
e->lineno,
e->col_offset + 1,
e->end_lineno,
e->end_col_offset + 1);
return 0;
}
if (st->st_cur->ste_comprehension) {
@ -1554,8 +1585,9 @@ symtable_visit_expr(struct symtable *st, expr_ty e)
if (e->v.Lambda.args->kw_defaults)
VISIT_SEQ_WITH_NULL(st, expr, e->v.Lambda.args->kw_defaults);
if (!symtable_enter_block(st, lambda,
FunctionBlock, (void *)e, e->lineno,
e->col_offset))
FunctionBlock, (void *)e,
e->lineno, e->col_offset,
e->end_lineno, e->end_col_offset))
VISIT_QUIT(st, 0);
VISIT(st, arguments, e->v.Lambda.args);
VISIT(st, expr, e->v.Lambda.body);
@ -1839,8 +1871,12 @@ symtable_visit_alias(struct symtable *st, alias_ty a)
if (st->st_cur->ste_type != ModuleBlock) {
int lineno = st->st_cur->ste_lineno;
int col_offset = st->st_cur->ste_col_offset;
int end_lineno = st->st_cur->ste_end_lineno;
int end_col_offset = st->st_cur->ste_end_col_offset;
PyErr_SetString(PyExc_SyntaxError, IMPORT_STAR_WARNING);
PyErr_SyntaxLocationObject(st->st_filename, lineno, col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
lineno, col_offset + 1,
end_lineno, end_col_offset + 1);
Py_DECREF(store_name);
return 0;
}
@ -1890,7 +1926,8 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
/* Create comprehension scope for the rest */
if (!scope_name ||
!symtable_enter_block(st, scope_name, FunctionBlock, (void *)e,
e->lineno, e->col_offset)) {
e->lineno, e->col_offset,
e->end_lineno, e->end_col_offset)) {
return 0;
}
if (outermost->is_async) {
@ -1919,9 +1956,11 @@ symtable_handle_comprehension(struct symtable *st, expr_ty e,
(e->kind == SetComp_kind) ? "'yield' inside set comprehension" :
(e->kind == DictComp_kind) ? "'yield' inside dict comprehension" :
"'yield' inside generator expression");
PyErr_SyntaxLocationObject(st->st_filename,
st->st_cur->ste_lineno,
st->st_cur->ste_col_offset + 1);
PyErr_RangedSyntaxLocationObject(st->st_filename,
st->st_cur->ste_lineno,
st->st_cur->ste_col_offset + 1,
st->st_cur->ste_end_lineno,
st->st_cur->ste_end_col_offset + 1);
symtable_exit_block(st);
return 0;
}