Skip to content

Commit

Permalink
Also test diagnostics.
Browse files Browse the repository at this point in the history
whitequark committed May 8, 2015
1 parent ccbcb3b commit 8eddbb1
Showing 2 changed files with 109 additions and 22 deletions.
34 changes: 19 additions & 15 deletions pyparser/parser.py
Original file line number Diff line number Diff line change
@@ -144,7 +144,7 @@ def rule(parser):

error_tok = parser._tokens[parser._errindex]
error = diagnostic.Diagnostic(
"error", "unexpected {actual}: expected {expected}",
"fatal", "unexpected {actual}: expected {expected}",
{'actual': error_tok.kind, 'expected': expected},
error_tok.loc)
raise diagnostic.DiagnosticException(error)
@@ -360,9 +360,6 @@ def _save(self):
return self._index

def _restore(self, data, rule):
if self._index == data:
return

self._index = data
self._token = self._tokens[self._index]

@@ -411,10 +408,10 @@ def _assignable(self, node, is_delete=False):
else:
if is_delete:
error = diagnostic.Diagnostic(
"error", "cannot delete this expression", {}, node.loc)
"fatal", "cannot delete this expression", {}, node.loc)
else:
error = diagnostic.Diagnostic(
"error", "cannot assign to this expression", {}, node.loc)
"fatal", "cannot assign to this expression", {}, node.loc)
raise diagnostic.DiagnosticException(error)

def _empty_arguments(self):
@@ -537,16 +534,18 @@ def varargslist(self, fparams, args):
('*' NAME [',' '**' NAME] | '**' NAME) |
fpdef ['=' test] (',' fpdef ['=' test])* [','])"""
for fparam, default_opt in fparams:
args.args.append(fparam)
if default_opt:
equals_loc, default = default_opt
args.equals_locs.append(equals_loc)
args.defaults.append(default)
elif len(args.defaults) > 0:
error = diagnostic.Diagnostic(
"error", "non-default argument follows default argument", {}, fparam.loc)
"fatal", "non-default argument follows default argument", {},
fparam.loc, [args.args[-1].loc.join(args.defaults[-1].loc)])
raise diagnostic.DiagnosticException(error)

args.args.append(fparam)

def fparam_loc(fparam, default_opt):
if default_opt:
equals_loc, default = default_opt
@@ -603,9 +602,10 @@ def expr_stmt(self, lhs, rhs):
"""expr_stmt: testlist (augassign (yield_expr|testlist) |
('=' (yield_expr|testlist))*)"""
if isinstance(rhs, ast.AugAssign):
if isinstance(lhs, ast.Tuple):
if isinstance(lhs, ast.Tuple) or isinstance(lhs, ast.List):
error = diagnostic.Diagnostic(
"error", "illegal expression for augmented assignment", {}, rhs.loc)
"fatal", "illegal expression for augmented assignment", {},
rhs.op.loc, [lhs.loc])
raise diagnostic.DiagnosticException(error)
else:
rhs.target = self._assignable(lhs)
@@ -859,14 +859,16 @@ def if_stmt(self, if_loc, test, if_colon_loc, body, elifs, else_opt):
for elif_ in elifs:
stmt.keyword_loc, stmt.test, stmt.if_colon_loc, stmt.body = elif_
stmt.loc = stmt.keyword_loc.join(stmt.body[-1].loc)
if stmt.orelse: stmt.loc = stmt.loc.join(stmt.orelse[-1].loc)
if stmt.orelse:
stmt.loc = stmt.loc.join(stmt.orelse[-1].loc)
stmt = ast.If(orelse=[stmt],
else_loc=None, else_colon_loc=None)

stmt.keyword_loc, stmt.test, stmt.if_colon_loc, stmt.body = \
if_loc, test, if_colon_loc, body
stmt.loc = stmt.keyword_loc.join(stmt.body[-1].loc)
if stmt.orelse: stmt.loc = stmt.loc.join(stmt.orelse[-1].loc)
if stmt.orelse:
stmt.loc = stmt.loc.join(stmt.orelse[-1].loc)
return stmt

@action(Seq(Loc('while'), Rule('test'), Loc(':'), Rule('suite'),
@@ -1307,7 +1309,8 @@ def arglist_2(self, star_loc, stararg, postargs, kwarg_opt):
for postarg in postargs:
if not isinstance(postarg, ast.keyword):
error = diagnostic.Diagnostic(
"error", "only named arguments may follow *expression", {}, postarg.loc)
"fatal", "only named arguments may follow *expression", {},
postarg.loc, [star_loc.join(stararg.loc)])
raise diagnostic.DiagnosticException(error)

return postargs, \
@@ -1344,7 +1347,8 @@ def arglist(self, pre_args, rest):
call.keywords.append(arg)
elif len(call.keywords) > 0:
error = diagnostic.Diagnostic(
"error", "non-keyword arg after keyword arg", {}, arg.loc)
"fatal", "non-keyword arg after keyword arg", {},
arg.loc, [call.keywords[-1].loc])
raise diagnostic.DiagnosticException(error)
else:
call.args.append(arg)
@@ -1355,7 +1359,7 @@ def argument_1(self, equals_loc, rhs):
def thunk(lhs):
if not isinstance(lhs, ast.Name):
error = diagnostic.Diagnostic(
"error", "keyword must be an identifier", {}, lhs.loc)
"fatal", "keyword must be an identifier", {}, lhs.loc)
raise diagnostic.DiagnosticException(error)
return ast.keyword(arg=lhs.id, value=rhs,
loc=lhs.loc.join(rhs.loc),
97 changes: 90 additions & 7 deletions pyparser/test/test_parser.py
Original file line number Diff line number Diff line change
@@ -69,7 +69,7 @@ def flatten_python_ast(self, node):
_loc_re = re.compile(r"\s*([~^]*)<?\s+([a-z_0-9.]+)")
_path_re = re.compile(r"(([a-z_]+)|([0-9]+))(\.)?")

def match_loc(self, ast, matcher, root=lambda x: x):
def match_loc(self, ast, matcher, root=lambda x: (0, x)):
offset, ast = root(ast)

matcher_pos = 0
@@ -142,24 +142,24 @@ def assertParsesToplevel(self, expected_flat_ast, code,
ast = getattr(self.parser_for(code, interactive=interactive), mode)()
self.assertEqual(expected_flat_ast, self.flatten_ast(ast))

def assertDiagnoses(self, code, diag):
def assertDiagnoses(self, code, level, reason, args={}, loc_matcher=""):
try:
self.parser_for(code).file_input()
self.fail("Expected a diagnostic")
except diagnostic.DiagnosticException as e:
level, reason, args, loc = diag
self.assertEqual(level, e.diagnostic.level)
self.assertEqual(reason, e.diagnostic.reason)
for key in args:
self.assertEqual(args[key], e.diagnostic.arguments[key],
"{{%s}}: \"%s\" != \"%s\"" %
(key, args[key], e.diagnostic.arguments[key]))
self.assertEqual(source.Range(self.source_buffer, *loc),
e.diagnostic.location)
self.match_loc([e.diagnostic.location] + e.diagnostic.highlights,
loc_matcher)

def assertDiagnosesUnexpected(self, code, err_token, loc):
def assertDiagnosesUnexpected(self, code, err_token, loc_matcher=""):
self.assertDiagnoses(code,
("error", "unexpected {actual}: expected {expected}", {'actual': err_token}, loc))
"fatal", "unexpected {actual}: expected {expected}",
{'actual': err_token}, loc_matcher="")

# Fixtures

@@ -806,6 +806,14 @@ def test_assign(self):
"x = yield y",
"~~~~~~~~~~~ 0.loc")

def test_assign_tuplerhs(self):
self.assertParsesSuite(
[{'ty': 'Assign', 'targets': [self.ast_x], 'value':
{'ty': 'Tuple', 'ctx': None, 'elts': [self.ast_1, self.ast_2]}}],
"x = 1, 2",
" ~~~~ 0.value.loc"
"~~~~~~~~ 0.loc")

def test_augassign(self):
self.assertParsesSuite(
[{'ty': 'AugAssign', 'op': {'ty': 'Add'}, 'target': self.ast_x, 'value': self.ast_1}],
@@ -905,6 +913,13 @@ def test_print(self):
" ~~ 0.dest_loc"
"~~~~~~~~~~~~ 0.loc")

self.assertParsesSuite(
[{'ty': 'Print', 'dest': self.ast_2, 'values': [self.ast_1], 'nl': False}],
"print >>2, 1,",
"~~~~~ 0.keyword_loc"
" ~~ 0.dest_loc"
"~~~~~~~~~~~~~ 0.loc")

def test_del(self):
self.assertParsesSuite(
[{'ty': 'Delete', 'targets': [self.ast_x]}],
@@ -1520,3 +1535,71 @@ def test_future_print(self):
{'ty': 'Call', 'func': {'ty': 'Name', 'id': 'print', 'ctx': None},
'starargs': None, 'kwargs': None, 'args': [self.ast_x], 'keywords': []}}],
"from __future__ import print_function·print(x)")

#
# DIAGNOSTICS
#

def test_diag_assignable(self):
self.assertDiagnoses(
"1 = 1",
'fatal', "cannot assign to this expression", {},
"^ 0")

self.assertDiagnoses(
"[1] = 1",
'fatal', "cannot assign to this expression", {},
" ^ 0")

self.assertDiagnoses(
"x() = 1",
'fatal', "cannot assign to this expression", {},
"~~~ 0")

self.assertDiagnoses(
"del 1",
'fatal', "cannot delete this expression", {},
" ^ 0")

def test_diag_def(self):
self.assertDiagnoses(
"def x(y=1, z): pass",
'fatal', "non-default argument follows default argument", {},
" ^ 0"
" ~~~ 1")

def test_diag_augassign(self):
self.assertDiagnoses(
"(1,) += 1",
'fatal', "illegal expression for augmented assignment", {},
" ^^ 0"
"~~~~ 1")

self.assertDiagnoses(
"[1] += 1",
'fatal', "illegal expression for augmented assignment", {},
" ^^ 0"
"~~~ 1")

def test_diag_call(self):
self.assertDiagnoses(
"x(*y, z)",
'fatal', "only named arguments may follow *expression", {},
" ^ 0"
" ~~ 1")

self.assertDiagnoses(
"x(y=1, z)",
'fatal', "non-keyword arg after keyword arg", {},
" ^ 0"
" ~~~ 1")

self.assertDiagnoses(
"x(1=1)",
'fatal', "keyword must be an identifier", {},
" ^ 0")

def test_diag_generic(self):
self.assertDiagnosesUnexpected(
"x + ,", ",",
" ^ 0")

0 comments on commit 8eddbb1

Please sign in to comment.