Parser: Useful error messages when a Python keyword is used. -- MOS_MIGRATED_REVID=89307831
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java index 3ce949d..297aa13 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Parser.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Parser.java
@@ -299,6 +299,31 @@ return previous; } + // Keywords that exist in Python and that we don't parse. + private static final EnumSet<TokenKind> FORBIDDEN_KEYWORDS = + EnumSet.of(TokenKind.AS, TokenKind.ASSERT, TokenKind.BREAK, TokenKind.CONTINUE, + TokenKind.DEL, TokenKind.EXCEPT, TokenKind.FINALLY, TokenKind.FROM, TokenKind.GLOBAL, + TokenKind.IMPORT, TokenKind.IS, TokenKind.LAMBDA, TokenKind.NONLOCAL, TokenKind.RAISE, + TokenKind.TRY, TokenKind.WITH, TokenKind.WHILE, TokenKind.YIELD); + + private void checkForbiddenKeywords(Token token) { + if (parsePython || !FORBIDDEN_KEYWORDS.contains(token.kind)) { + return; + } + String error; + switch (token.kind) { + case ASSERT: error = "'assert' not supported, use 'fail' instead"; break; + case TRY: error = "'try' not supported, all exceptions are fatal"; break; + case IMPORT: error = "'import' not supported, use 'load' instead"; break; + case IS: error = "'is' not supported, use '==' instead"; break; + case LAMBDA: error = "'lambda' not supported, declare a function instead"; break; + case RAISE: error = "'raise' not supported, use 'fail' instead"; break; + case WHILE: error = "'while' not supported, use 'for' instead"; break; + default: error = "keyword '" + token.kind.getPrettyName() + "' not supported"; break; + } + reportError(lexer.createLocation(token.left, token.right), error); + } + private void nextToken() { if (pushedToken != null) { token = pushedToken; @@ -313,6 +338,7 @@ } } } + checkForbiddenKeywords(token); if (DEBUGGING) { System.err.print(token); }
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java index afca4b3..683f2c7 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/ParserTest.java
@@ -316,8 +316,16 @@ public void testAssignKeyword() { syntaxEvents.setFailFast(false); parseExpr("with = 4"); + syntaxEvents.assertContainsEvent("keyword 'with' not supported"); syntaxEvents.assertContainsEvent("syntax error at 'with': expected expression"); - syntaxEvents.collector().clear(); + } + + @Test + public void testTry() { + syntaxEvents.setFailFast(false); + parseExpr("try: 1 + 1"); + syntaxEvents.assertContainsEvent("'try' not supported, all exceptions are fatal"); + syntaxEvents.assertContainsEvent("syntax error at 'try': expected expression"); } @Test @@ -754,6 +762,21 @@ } @Test + public void testFunctionDefinitionIgnoredEvenWithUnsupportedKeyword() throws Exception { + // Parser skips over entire function definitions without reporting error, + // when parsePython is set to true. + List<Statement> stmts = parseFile( + "x = 1;\n" + + "def foo(x, y, **z):\n" + + " try:\n" + + " x = 2\n" + + " with: pass\n" + + " return 2\n" + + "x = 3", true /* parsePython */); + assertThat(stmts).hasSize(2); + } + + @Test public void testFunctionDefinitionIgnored() throws Exception { // Parser skips over entire function definitions without reporting error, // when parsePython is set to true.