Skip to content

Commit

Permalink
Improve syntax errors when a reserved keyword is used as an identifier (
Browse files Browse the repository at this point in the history
#3389)

With this improper use of reserved keywords will produce better errors,
e.g:

    edgedb> WITH select := '1' SELECT 1;
    error: EdgeQLSyntaxError: Unexpected keyword 'select'
      ┌─ query:1:6
      │
    1 │ WITH select := '1' SELECT 1;
      │      ^^^^^^ Use a different identifier or quote the name with backticks: `select`
      │
      = Token 'select' is a reserved keyword and cannot be used as an identifier
  • Loading branch information
elprans committed Jan 31, 2022
1 parent 23aa076 commit b39dff8
Show file tree
Hide file tree
Showing 3 changed files with 34 additions and 15 deletions.
21 changes: 20 additions & 1 deletion edb/edgeql/parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from .grammar import rust_lexer, tokens
from .grammar import expressions as gr_exprs
from .grammar import commondl as gr_commondl
from .grammar import keywords as gr_keywords


class EdgeQLParserBase(parsing.Parser):
Expand All @@ -34,6 +35,7 @@ def get_debug(self):

def get_exception(self, native_err, context, token=None):
msg = native_err.args[0]
details = None
hint = None

if isinstance(native_err, errors.EdgeQLSyntaxError):
Expand All @@ -44,6 +46,11 @@ def get_exception(self, native_err, context, token=None):
token_kind = token.kind()
ltok = self.parser._stack[-1][0]

is_reserved = (
token.text().lower()
in gr_keywords.by_type[gr_keywords.RESERVED_KEYWORD]
)

# Look at the parsing stack and use tokens and
# non-terminals to infer the parser rule when the
# error occurred.
Expand Down Expand Up @@ -129,11 +136,23 @@ def get_exception(self, native_err, context, token=None):
msg = f'Unexpected {token.val!r}'
elif token_kind == 'NL':
msg = 'Unexpected end of line'
elif is_reserved and not isinstance(ltok, gr_exprs.Expr):
# Another token followed by a reserved keyword:
# likely an attempt to use keyword as identifier
msg = f'Unexpected keyword {token.text()!r}'
details = (
f'Token {token.text()!r} is a reserved keyword and'
f' cannot be used as an identifier'
)
hint = (
f'Use a different identifier or quote the name with'
f' backticks: `{token.text()}`'
)
else:
msg = f'Unexpected {token.text()!r}'

return errors.EdgeQLSyntaxError(
msg, hint=hint, context=context, token=token)
msg, details=details, hint=hint, context=context, token=token)

def _get_rule(self):
ltok = self.parser._stack[-1][0]
Expand Down
24 changes: 12 additions & 12 deletions tests/test_edgeql_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,7 @@ def test_edgeql_syntax_struct_07(self):
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
"Unexpected 'if'", line=4, col=13)
"Unexpected keyword 'if'", line=4, col=13)
def test_edgeql_syntax_struct_08(self):
"""
SELECT (
Expand Down Expand Up @@ -1995,14 +1995,14 @@ def test_edgeql_syntax_path_17(self):
SELECT ..foo;
"""

@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected '__source__'",
@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected keyword '__source__'",
line=2, col=20)
def test_edgeql_syntax_path_18(self):
"""
SELECT Foo.__source__;
"""

@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected '__subject__'",
@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected keyword '__subject__'",
line=2, col=20)
def test_edgeql_syntax_path_19(self):
"""
Expand Down Expand Up @@ -2036,14 +2036,14 @@ def test_edgeql_syntax_path_22(self):
SELECT TUP.0.2e2;
"""

@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected '__type__'",
@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected keyword '__type__'",
line=2, col=16)
def test_edgeql_syntax_path_23(self):
"""
SELECT __type__;
"""

@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected '__type__'",
@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected keyword '__type__'",
line=2, col=24)
def test_edgeql_syntax_path_24(self):
"""
Expand Down Expand Up @@ -3289,7 +3289,7 @@ def test_edgeql_syntax_function_03(self):
# NOTE: this test is a remnant of an attempt to define syntax for
# window functions. It may become valid again.
@tb.must_fail(errors.EdgeQLSyntaxError,
r"Unexpected 'OVER'", line=2, col=36)
r"Unexpected keyword 'OVER'", line=2, col=36)
def test_edgeql_syntax_function_04(self):
"""
SELECT some_agg(User.name) OVER (ORDER BY User.age ASC);
Expand Down Expand Up @@ -3803,7 +3803,7 @@ def test_edgeql_syntax_ddl_role_01(self):
EXTENDING delegated, `mytest"baserole"`;
"""

@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected 'if'",
@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected keyword 'if'",
line=2, col=21)
def test_edgeql_syntax_ddl_role_02(self):
"""
Expand Down Expand Up @@ -4088,7 +4088,7 @@ def test_edgeql_syntax_ddl_scalar_01(self):
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
"Unexpected 'anytype'", line=2, col=28)
"Unexpected keyword 'anytype'", line=2, col=28)
def test_edgeql_syntax_ddl_scalar_02(self):
"""
CREATE SCALAR TYPE anytype EXTENDING int64;
Expand Down Expand Up @@ -4493,22 +4493,22 @@ def test_edgeql_syntax_ddl_function_30(self):
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
"Unexpected 'SET'", line=2, col=43)
"Unexpected keyword 'SET'", line=2, col=43)
def test_edgeql_syntax_ddl_function_31(self):
# parameter name is missing
"""
CREATE FUNCTION std::foo(VARIADIC SET OF std::str) -> std::int64;
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
"Unexpected 'SET'", line=2, col=43)
"Unexpected keyword 'SET'", line=2, col=43)
def test_edgeql_syntax_ddl_function_32(self):
"""
CREATE FUNCTION std::foo(VARIADIC SET OF std::str) -> std::int64;
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
"Unexpected 'VARIADIC'", line=2, col=39)
"Unexpected keyword 'VARIADIC'", line=2, col=39)
def test_edgeql_syntax_ddl_function_33(self):
"""
CREATE FUNCTION std::foo(bar: VARIADIC SET OF std::str) -> std::int64;
Expand All @@ -4521,7 +4521,7 @@ def test_edgeql_syntax_ddl_function_34(self):
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
"Unexpected 'VARIADIC'", line=2, col=57)
"Unexpected keyword 'VARIADIC'", line=2, col=57)
def test_edgeql_syntax_ddl_function_35(self):
"""
CREATE FUNCTION std::foo(a: SET OF std::str) -> VARIADIC std::int64
Expand Down
4 changes: 2 additions & 2 deletions tests/test_schema_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def test_eschema_syntax_type_10(self):
};
"""

@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected 'Commit'",
@tb.must_fail(errors.EdgeQLSyntaxError, "Unexpected keyword 'Commit'",
line=3, col=18)
def test_eschema_syntax_type_11(self):
"""
Expand Down Expand Up @@ -1659,7 +1659,7 @@ def test_eschema_syntax_annotation_13(self):
"""

@tb.must_fail(errors.EdgeQLSyntaxError,
r"Unexpected 'extending'", line=3, col=46)
r"Unexpected keyword 'extending'", line=3, col=46)
def test_eschema_syntax_annotation_14(self):
"""
module test {
Expand Down

0 comments on commit b39dff8

Please sign in to comment.