From d616d70c504ed3f63aece45f42f9c53d80e92ca9 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Sun, 6 Sep 2015 20:04:50 +0300 Subject: [PATCH] Parse and highlight the 'async' keyword (PY-16094) We had to switch to stack-based parsing contexts in order to be able to use context-sensitive token filtering for 'async'. Also this commit fixes the highlighting annotator issue with blinking names of the functions and blinking 'async'. --- .../plugins/ipnb/psi/IpnbPyParser.java | 2 +- .../com/jetbrains/python/PyTokenTypes.java | 1 + python/src/META-INF/python-core.xml | 2 + .../python/PythonTokenSetContributor.java | 2 +- .../console/parsing/PyConsoleParser.java | 2 +- .../parsing/PyConsoleParsingContext.java | 9 +- .../python/parsing/FunctionParsing.java | 23 +- .../python/parsing/ParsingContext.java | 21 ++ .../python/parsing/ParsingScope.java | 14 ++ .../jetbrains/python/parsing/PyParser.java | 9 +- .../python/parsing/StatementParsing.java | 211 ++++++++++-------- .../DumbAwareHighlightingAnnotator.java | 62 +++++ python/testData/highlighting/async.py | 28 +++ .../highlighting/returnOutsideOfFunction.py | 4 +- .../returnWithArgumentsInGenerator.py | 2 +- python/testData/psi/AsyncDef.py | 15 ++ python/testData/psi/AsyncDef.txt | 80 +++++++ python/testData/psi/AsyncFor.py | 7 + python/testData/psi/AsyncFor.txt | 54 +++++ python/testData/psi/AsyncWith.py | 6 + python/testData/psi/AsyncWith.txt | 44 ++++ .../python/PythonHighlightingTest.java | 4 + .../jetbrains/python/PythonParsingTest.java | 12 + 23 files changed, 503 insertions(+), 111 deletions(-) create mode 100644 python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java create mode 100644 python/testData/highlighting/async.py create mode 100644 python/testData/psi/AsyncDef.py create mode 100644 python/testData/psi/AsyncDef.txt create mode 100644 python/testData/psi/AsyncFor.py create mode 100644 python/testData/psi/AsyncFor.txt create mode 100644 python/testData/psi/AsyncWith.py create mode 100644 python/testData/psi/AsyncWith.txt diff --git a/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyParser.java b/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyParser.java index 53e0de51af83..093b03a3b972 100644 --- a/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyParser.java +++ b/python/ipnb/src/org/jetbrains/plugins/ipnb/psi/IpnbPyParser.java @@ -32,7 +32,7 @@ public class IpnbPyParser extends PyParser { builder.setTokenTypeRemapper(statementParser); while (!builder.eof()) { - statementParser.parseStatement(context.emptyParsingScope()); + statementParser.parseStatement(); } rootMarker.done(root); return builder.getTreeBuilt(); diff --git a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java index 5d3060e155a6..be858df92181 100644 --- a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java +++ b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java @@ -73,6 +73,7 @@ public class PyTokenTypes { public static final PyElementType FALSE_KEYWORD = new PyElementType("FALSE_KEYWORD"); public static final PyElementType NONLOCAL_KEYWORD = new PyElementType("NONLOCAL_KEYWORD"); public static final PyElementType DEBUG_KEYWORD = new PyElementType("DEBUG_KEYWORD"); + public static final PyElementType ASYNC_KEYWORD = new PyElementType("ASYNC_KEYWORD"); public static final PyElementType INTEGER_LITERAL = new PyElementType("INTEGER_LITERAL"); public static final PyElementType FLOAT_LITERAL = new PyElementType("FLOAT_LITERAL"); diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index 29489c86a618..3ed392a62d48 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -41,6 +41,7 @@ + @@ -614,6 +615,7 @@ + diff --git a/python/src/com/jetbrains/python/PythonTokenSetContributor.java b/python/src/com/jetbrains/python/PythonTokenSetContributor.java index 891eff28c507..51a42589109b 100644 --- a/python/src/com/jetbrains/python/PythonTokenSetContributor.java +++ b/python/src/com/jetbrains/python/PythonTokenSetContributor.java @@ -73,7 +73,7 @@ public class PythonTokenSetContributor extends PythonDialectsTokenSetContributor LAMBDA_KEYWORD, NOT_KEYWORD, OR_KEYWORD, PASS_KEYWORD, PRINT_KEYWORD, RAISE_KEYWORD, RETURN_KEYWORD, TRY_KEYWORD, WITH_KEYWORD, WHILE_KEYWORD, YIELD_KEYWORD, - NONE_KEYWORD, TRUE_KEYWORD, FALSE_KEYWORD, NONLOCAL_KEYWORD, DEBUG_KEYWORD); + NONE_KEYWORD, TRUE_KEYWORD, FALSE_KEYWORD, NONLOCAL_KEYWORD, DEBUG_KEYWORD, ASYNC_KEYWORD); } @NotNull diff --git a/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java b/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java index 100330608c73..2d71ee03a685 100644 --- a/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java +++ b/python/src/com/jetbrains/python/console/parsing/PyConsoleParser.java @@ -56,7 +56,7 @@ public class PyConsoleParser extends PyParser{ builder.setTokenTypeRemapper(stmt_parser); // must be done before touching the caching lexer with eof() call. while (!builder.eof()) { - stmt_parser.parseStatement(context.emptyParsingScope()); + stmt_parser.parseStatement(); } rootMarker.done(root); return builder.getTreeBuilt(); diff --git a/python/src/com/jetbrains/python/console/parsing/PyConsoleParsingContext.java b/python/src/com/jetbrains/python/console/parsing/PyConsoleParsingContext.java index 32e8882b38b7..8c6aa7e33b8c 100644 --- a/python/src/com/jetbrains/python/console/parsing/PyConsoleParsingContext.java +++ b/python/src/com/jetbrains/python/console/parsing/PyConsoleParsingContext.java @@ -72,7 +72,7 @@ public class PyConsoleParsingContext extends ParsingContext { @Override - public void parseStatement(ParsingScope scope) { + public void parseStatement() { if (myStartsWithIPythonSymbol) { parseIPythonCommand(); } @@ -89,7 +89,7 @@ public class PyConsoleParsingContext extends ParsingContext { myBuilder.advanceLexer(); } } - super.parseStatement(scope); + super.parseStatement(); } } @@ -101,13 +101,14 @@ public class PyConsoleParsingContext extends ParsingContext { ipythonCommand.done(PyElementTypes.EMPTY_EXPRESSION); } - protected void checkEndOfStatement(ParsingScope scope) { + protected void checkEndOfStatement() { if (myPythonConsoleData.isIPythonEnabled()) { PsiBuilder builder = myContext.getBuilder(); if (builder.getTokenType() == PyTokenTypes.STATEMENT_BREAK) { builder.advanceLexer(); } else if (builder.getTokenType() == PyTokenTypes.SEMICOLON) { + final ParsingScope scope = getParsingContext().getScope(); if (!scope.isSuite()) { builder.advanceLexer(); if (builder.getTokenType() == PyTokenTypes.STATEMENT_BREAK) { @@ -131,7 +132,7 @@ public class PyConsoleParsingContext extends ParsingContext { } } else { - super.checkEndOfStatement(scope); + super.checkEndOfStatement(); } } } diff --git a/python/src/com/jetbrains/python/parsing/FunctionParsing.java b/python/src/com/jetbrains/python/parsing/FunctionParsing.java index 60b32e91292c..2c8b7dff584b 100644 --- a/python/src/com/jetbrains/python/parsing/FunctionParsing.java +++ b/python/src/com/jetbrains/python/parsing/FunctionParsing.java @@ -20,6 +20,7 @@ import com.intellij.lang.WhitespacesBinders; import com.intellij.psi.tree.IElementType; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyTokenTypes; +import org.jetbrains.annotations.NotNull; import static com.jetbrains.python.PyBundle.message; @@ -33,23 +34,25 @@ public class FunctionParsing extends Parsing { super(context); } - public void parseFunctionDeclaration() { + public void parseFunctionDeclaration(@NotNull PsiBuilder.Marker endMarker) { assertCurrentToken(PyTokenTypes.DEF_KEYWORD); - final PsiBuilder.Marker functionMarker = myBuilder.mark(); - parseFunctionInnards(functionMarker); + parseFunctionInnards(endMarker); } protected IElementType getFunctionType() { return FUNCTION_TYPE; } - protected void parseFunctionInnards(PsiBuilder.Marker functionMarker) { + protected void parseFunctionInnards(@NotNull PsiBuilder.Marker functionMarker) { myBuilder.advanceLexer(); parseIdentifierOrSkip(PyTokenTypes.LPAR); parseParameterList(); parseReturnTypeAnnotation(); checkMatches(PyTokenTypes.COLON, message("PARSE.expected.colon")); - getStatementParser().parseSuite(functionMarker, getFunctionType(), myContext.emptyParsingScope().withFunction(true)); + final ParsingContext context = getParsingContext(); + context.pushScope(context.getScope().withFunction(true)); + getStatementParser().parseSuite(functionMarker, getFunctionType()); + context.popScope(); } public void parseReturnTypeAnnotation() { @@ -63,7 +66,7 @@ public class FunctionParsing extends Parsing { } } - public void parseDecoratedDeclaration(ParsingScope scope) { + public void parseDecoratedDeclaration() { assertCurrentToken(PyTokenTypes.AT); // ??? need this? final PsiBuilder.Marker decoratorStartMarker = myBuilder.mark(); final PsiBuilder.Marker decoListMarker = myBuilder.mark(); @@ -92,15 +95,15 @@ public class FunctionParsing extends Parsing { } if (decorated) decoListMarker.done(PyElementTypes.DECORATOR_LIST); //else decoListMarker.rollbackTo(); - parseDeclarationAfterDecorator(decoratorStartMarker, scope); + parseDeclarationAfterDecorator(decoratorStartMarker); } - protected void parseDeclarationAfterDecorator(PsiBuilder.Marker endMarker, ParsingScope scope) { + protected void parseDeclarationAfterDecorator(PsiBuilder.Marker endMarker) { if (myBuilder.getTokenType() == PyTokenTypes.DEF_KEYWORD) { - parseFunctionInnards(endMarker); // it calls endMarker.done() + parseFunctionInnards(endMarker); } else if (myBuilder.getTokenType() == PyTokenTypes.CLASS_KEYWORD) { - getStatementParser().parseClassDeclaration(endMarker, scope); + getStatementParser().parseClassDeclaration(endMarker); } else { myBuilder.error(message("PARSE.expected.@.or.def")); diff --git a/python/src/com/jetbrains/python/parsing/ParsingContext.java b/python/src/com/jetbrains/python/parsing/ParsingContext.java index 66f3ebb32e62..94253c07720a 100644 --- a/python/src/com/jetbrains/python/parsing/ParsingContext.java +++ b/python/src/com/jetbrains/python/parsing/ParsingContext.java @@ -17,6 +17,10 @@ package com.jetbrains.python.parsing; import com.intellij.lang.PsiBuilder; import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayDeque; +import java.util.Deque; public class ParsingContext { private final StatementParsing stmtParser; @@ -24,6 +28,7 @@ public class ParsingContext { private final FunctionParsing functionParser; private final PsiBuilder myBuilder; private final LanguageLevel myLanguageLevel; + private final Deque myScopes; public ParsingContext(final PsiBuilder builder, LanguageLevel languageLevel, StatementParsing.FUTURE futureFlag) { myBuilder = builder; @@ -31,6 +36,22 @@ public class ParsingContext { stmtParser = new StatementParsing(this, futureFlag); expressionParser = new ExpressionParsing(this); functionParser = new FunctionParsing(this); + myScopes = new ArrayDeque(); + myScopes.push(emptyParsingScope()); + } + + @NotNull + public ParsingScope popScope() { + return myScopes.pop(); + } + + public void pushScope(@NotNull ParsingScope scope) { + myScopes.push(scope); + } + + @NotNull + public ParsingScope getScope() { + return myScopes.peek(); } public StatementParsing getStatementParser() { diff --git a/python/src/com/jetbrains/python/parsing/ParsingScope.java b/python/src/com/jetbrains/python/parsing/ParsingScope.java index f029c2863a9a..d73721d52751 100644 --- a/python/src/com/jetbrains/python/parsing/ParsingScope.java +++ b/python/src/com/jetbrains/python/parsing/ParsingScope.java @@ -23,6 +23,9 @@ public class ParsingScope { private boolean myClass = false; private boolean mySuite = false; private boolean myAfterSemicolon = false; + private boolean myAsync = false; + + protected ParsingScope() {} public ParsingScope withFunction(boolean flag) { final ParsingScope result = copy(); @@ -42,6 +45,12 @@ public class ParsingScope { return result; } + public ParsingScope withAsync() { + final ParsingScope result = copy(); + result.myAsync = true; + return result; + } + public boolean isFunction() { return myFunction; } @@ -54,6 +63,10 @@ public class ParsingScope { return mySuite; } + public boolean isAsync() { + return myAsync; + } + public boolean isAfterSemicolon() { return myAfterSemicolon; } @@ -71,6 +84,7 @@ public class ParsingScope { result.myFunction = myFunction; result.myClass = myClass; result.mySuite = mySuite; + result.myAsync = myAsync; return result; } } diff --git a/python/src/com/jetbrains/python/parsing/PyParser.java b/python/src/com/jetbrains/python/parsing/PyParser.java index d7e431af46e3..631485670de8 100644 --- a/python/src/com/jetbrains/python/parsing/PyParser.java +++ b/python/src/com/jetbrains/python/parsing/PyParser.java @@ -49,14 +49,15 @@ public class PyParser implements PsiParser { builder.setTokenTypeRemapper(statementParser); // must be done before touching the caching lexer with eof() call. boolean lastAfterSemicolon = false; while (!builder.eof()) { - ParsingScope scope = context.emptyParsingScope(); + context.pushScope(context.emptyParsingScope()); if (lastAfterSemicolon) { - statementParser.parseSimpleStatement(scope); + statementParser.parseSimpleStatement(); } else { - statementParser.parseStatement(scope); + statementParser.parseStatement(); } - lastAfterSemicolon = scope.isAfterSemicolon(); + lastAfterSemicolon = context.getScope().isAfterSemicolon(); + context.popScope(); } rootMarker.done(root); ASTNode ast = builder.getTreeBuilt(); diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index 8de31cd09f27..bd6b423c033e 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -23,6 +23,7 @@ import com.intellij.psi.tree.TokenSet; import com.intellij.util.text.CharArrayUtil; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.psi.PyElementType; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; @@ -49,6 +50,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { @NonNls protected static final String TOK_FALSE = "False"; @NonNls protected static final String TOK_NONLOCAL = "nonlocal"; @NonNls protected static final String TOK_EXEC = "exec"; + @NonNls protected static final String TOK_ASYNC = "async"; private static final String EXPRESSION_EXPECTED = "Expression expected"; public static final String IDENTIFIER_EXPECTED = "Identifier expected"; @@ -86,7 +88,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { myBuilder.setTokenTypeRemapper(this); // clear cached token type } - public void parseStatement(ParsingScope scope) { + public void parseStatement() { while (myBuilder.getTokenType() == PyTokenTypes.STATEMENT_BREAK) { myBuilder.advanceLexer(); @@ -98,103 +100,107 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (firstToken == null) return; if (firstToken == PyTokenTypes.WHILE_KEYWORD) { - parseWhileStatement(scope); + parseWhileStatement(); return; } if (firstToken == PyTokenTypes.IF_KEYWORD) { - parseIfStatement(PyTokenTypes.IF_KEYWORD, PyTokenTypes.ELIF_KEYWORD, PyTokenTypes.ELSE_KEYWORD, PyElementTypes.IF_STATEMENT, scope); + parseIfStatement(PyTokenTypes.IF_KEYWORD, PyTokenTypes.ELIF_KEYWORD, PyTokenTypes.ELSE_KEYWORD, PyElementTypes.IF_STATEMENT); return; } if (firstToken == PyTokenTypes.FOR_KEYWORD) { - parseForStatement(scope); + parseForStatement(myBuilder.mark()); return; } if (firstToken == PyTokenTypes.TRY_KEYWORD) { - parseTryStatement(scope); + parseTryStatement(); return; } if (firstToken == PyTokenTypes.DEF_KEYWORD) { - getFunctionParser().parseFunctionDeclaration(); + getFunctionParser().parseFunctionDeclaration(myBuilder.mark()); return; } if (firstToken == PyTokenTypes.AT) { - getFunctionParser().parseDecoratedDeclaration(scope); + getFunctionParser().parseDecoratedDeclaration(); return; } if (firstToken == PyTokenTypes.CLASS_KEYWORD) { - parseClassDeclaration(scope); + parseClassDeclaration(); return; } if (firstToken == PyTokenTypes.WITH_KEYWORD) { - parseWithStatement(scope); + parseWithStatement(myBuilder.mark()); + return; + } + if (firstToken == PyTokenTypes.ASYNC_KEYWORD) { + parseAsyncStatement(); return; } - parseSimpleStatement(scope); + parseSimpleStatement(); } - protected void parseSimpleStatement(ParsingScope scope) { + protected void parseSimpleStatement() { PsiBuilder builder = myContext.getBuilder(); final IElementType firstToken = builder.getTokenType(); if (firstToken == null) { return; } if (firstToken == PyTokenTypes.PRINT_KEYWORD && hasPrintStatement()) { - parsePrintStatement(builder, scope); + parsePrintStatement(builder); return; } if (firstToken == PyTokenTypes.ASSERT_KEYWORD) { - parseAssertStatement(scope); + parseAssertStatement(); return; } if (firstToken == PyTokenTypes.BREAK_KEYWORD) { - parseKeywordStatement(builder, PyElementTypes.BREAK_STATEMENT, scope); + parseKeywordStatement(builder, PyElementTypes.BREAK_STATEMENT); return; } if (firstToken == PyTokenTypes.CONTINUE_KEYWORD) { - parseKeywordStatement(builder, PyElementTypes.CONTINUE_STATEMENT, scope); + parseKeywordStatement(builder, PyElementTypes.CONTINUE_STATEMENT); return; } if (firstToken == PyTokenTypes.DEL_KEYWORD) { - parseDelStatement(scope); + parseDelStatement(); return; } if (firstToken == PyTokenTypes.EXEC_KEYWORD) { - parseExecStatement(scope); + parseExecStatement(); return; } if (firstToken == PyTokenTypes.GLOBAL_KEYWORD) { - parseNameDefiningStatement(scope, PyElementTypes.GLOBAL_STATEMENT); + parseNameDefiningStatement(PyElementTypes.GLOBAL_STATEMENT); return; } if (firstToken == PyTokenTypes.NONLOCAL_KEYWORD) { - parseNameDefiningStatement(scope, PyElementTypes.NONLOCAL_STATEMENT); + parseNameDefiningStatement(PyElementTypes.NONLOCAL_STATEMENT); return; } if (firstToken == PyTokenTypes.IMPORT_KEYWORD) { - parseImportStatement(scope, PyElementTypes.IMPORT_STATEMENT, PyElementTypes.IMPORT_ELEMENT); + parseImportStatement(PyElementTypes.IMPORT_STATEMENT, PyElementTypes.IMPORT_ELEMENT); return; } if (firstToken == PyTokenTypes.FROM_KEYWORD) { - parseFromImportStatement(scope); + parseFromImportStatement(); return; } if (firstToken == PyTokenTypes.PASS_KEYWORD) { - parseKeywordStatement(builder, PyElementTypes.PASS_STATEMENT, scope); + parseKeywordStatement(builder, PyElementTypes.PASS_STATEMENT); return; } if (firstToken == PyTokenTypes.RETURN_KEYWORD) { - parseReturnStatement(builder, scope); + parseReturnStatement(builder); return; } if (firstToken == PyTokenTypes.RAISE_KEYWORD) { - parseRaiseStatement(scope); + parseRaiseStatement(); return; } PsiBuilder.Marker exprStatement = builder.mark(); if (builder.getTokenType() == PyTokenTypes.YIELD_KEYWORD) { getExpressionParser().parseYieldOrTupleExpression(false); - checkEndOfStatement(scope); + checkEndOfStatement(); exprStatement.done(PyElementTypes.EXPRESSION_STATEMENT); return; } @@ -242,7 +248,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } } - checkEndOfStatement(scope); + checkEndOfStatement(); exprStatement.done(statementType); return; } @@ -270,8 +276,9 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { return myContext.getLanguageLevel().hasPrintStatement() && !myFutureFlags.contains(FUTURE.PRINT_FUNCTION); } - protected void checkEndOfStatement(ParsingScope scope) { + protected void checkEndOfStatement() { PsiBuilder builder = myContext.getBuilder(); + final ParsingScope scope = getParsingContext().getScope(); if (builder.getTokenType() == PyTokenTypes.STATEMENT_BREAK) { builder.advanceLexer(); scope.setAfterSemicolon(false); @@ -291,7 +298,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } } - private void parsePrintStatement(final PsiBuilder builder, ParsingScope scope) { + private void parsePrintStatement(final PsiBuilder builder) { LOG.assertTrue(builder.getTokenType() == PyTokenTypes.PRINT_KEYWORD); final PsiBuilder.Marker statement = builder.mark(); builder.advanceLexer(); @@ -311,29 +318,29 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } getExpressionParser().parseSingleExpression(false); } - checkEndOfStatement(scope); + checkEndOfStatement(); statement.done(PyElementTypes.PRINT_STATEMENT); } - protected void parseKeywordStatement(PsiBuilder builder, IElementType statementType, ParsingScope scope) { + protected void parseKeywordStatement(PsiBuilder builder, IElementType statementType) { final PsiBuilder.Marker statement = builder.mark(); builder.advanceLexer(); - checkEndOfStatement(scope); + checkEndOfStatement(); statement.done(statementType); } - private void parseReturnStatement(PsiBuilder builder, ParsingScope inSuite) { + private void parseReturnStatement(PsiBuilder builder) { LOG.assertTrue(builder.getTokenType() == PyTokenTypes.RETURN_KEYWORD); final PsiBuilder.Marker returnStatement = builder.mark(); builder.advanceLexer(); if (builder.getTokenType() != null && !getEndOfStatementsTokens().contains(builder.getTokenType())) { getExpressionParser().parseExpression(); } - checkEndOfStatement(inSuite); + checkEndOfStatement(); returnStatement.done(PyElementTypes.RETURN_STATEMENT); } - private void parseDelStatement(ParsingScope inSuite) { + private void parseDelStatement() { assertCurrentToken(PyTokenTypes.DEL_KEYWORD); final PsiBuilder.Marker delStatement = myBuilder.mark(); myBuilder.advanceLexer(); @@ -349,11 +356,11 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } } - checkEndOfStatement(inSuite); + checkEndOfStatement(); delStatement.done(PyElementTypes.DEL_STATEMENT); } - private void parseRaiseStatement(ParsingScope inSuite) { + private void parseRaiseStatement() { assertCurrentToken(PyTokenTypes.RAISE_KEYWORD); final PsiBuilder.Marker raiseStatement = myBuilder.mark(); myBuilder.advanceLexer(); @@ -374,11 +381,11 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } } } - checkEndOfStatement(inSuite); + checkEndOfStatement(); raiseStatement.done(PyElementTypes.RAISE_STATEMENT); } - private void parseAssertStatement(ParsingScope scope) { + private void parseAssertStatement() { assertCurrentToken(PyTokenTypes.ASSERT_KEYWORD); final PsiBuilder.Marker assertStatement = myBuilder.mark(); myBuilder.advanceLexer(); @@ -389,7 +396,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { myContext.getBuilder().error(EXPRESSION_EXPECTED); } } - checkEndOfStatement(scope); + checkEndOfStatement(); } else { myContext.getBuilder().error(EXPRESSION_EXPECTED); @@ -397,12 +404,12 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { assertStatement.done(PyElementTypes.ASSERT_STATEMENT); } - protected void parseImportStatement(ParsingScope scope, IElementType statementType, IElementType elementType) { + protected void parseImportStatement(IElementType statementType, IElementType elementType) { final PsiBuilder builder = myContext.getBuilder(); final PsiBuilder.Marker importStatement = builder.mark(); builder.advanceLexer(); parseImportElements(elementType, true, false, false); - checkEndOfStatement(scope); + checkEndOfStatement(); importStatement.done(statementType); } @@ -411,7 +418,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { from identifier import id, id... -- may be either relative or absolute from . import identifier -- only relative */ - private void parseFromImportStatement(ParsingScope inSuite) { + private void parseFromImportStatement() { PsiBuilder builder = myContext.getBuilder(); assertCurrentToken(PyTokenTypes.FROM_KEYWORD); myFutureImportPhase = Phase.FROM; @@ -447,7 +454,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { statementType = types.statement; parseImportElements(types.element, false, false, from_future); } - checkEndOfStatement(inSuite); + checkEndOfStatement(); fromImportStatement.done(statementType); myFutureImportPhase = Phase.NONE; } @@ -563,7 +570,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { return true; } - private void parseNameDefiningStatement(ParsingScope scope, final PyElementType elementType) { + private void parseNameDefiningStatement(final PyElementType elementType) { final PsiBuilder.Marker globalStatement = myBuilder.mark(); myBuilder.advanceLexer(); parseIdentifier(PyElementTypes.TARGET_EXPRESSION); @@ -571,11 +578,11 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { myBuilder.advanceLexer(); parseIdentifier(PyElementTypes.TARGET_EXPRESSION); } - checkEndOfStatement(scope); + checkEndOfStatement(); globalStatement.done(elementType); } - private void parseExecStatement(ParsingScope inSuite) { + private void parseExecStatement() { assertCurrentToken(PyTokenTypes.EXEC_KEYWORD); final PsiBuilder.Marker execStatement = myBuilder.mark(); myBuilder.advanceLexer(); @@ -588,12 +595,12 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { getExpressionParser().parseSingleExpression(false); } } - checkEndOfStatement(inSuite); + checkEndOfStatement(); execStatement.done(PyElementTypes.EXEC_STATEMENT); } - protected void parseIfStatement(PyElementType ifKeyword, PyElementType elifKeyword, PyElementType elseKeyword, PyElementType elementType, - ParsingScope scope) { + protected void parseIfStatement(PyElementType ifKeyword, PyElementType elifKeyword, PyElementType elseKeyword, + PyElementType elementType) { assertCurrentToken(ifKeyword); final PsiBuilder.Marker ifStatement = myBuilder.mark(); final PsiBuilder.Marker ifPart = myBuilder.mark(); @@ -601,7 +608,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error("expression expected"); } - parseColonAndSuite(scope); + parseColonAndSuite(); ifPart.done(PyElementTypes.IF_PART_IF); PsiBuilder.Marker elifPart = myBuilder.mark(); while (myBuilder.getTokenType() == elifKeyword) { @@ -609,7 +616,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error("expression expected"); } - parseColonAndSuite(scope); + parseColonAndSuite(); elifPart.done(PyElementTypes.IF_PART_ELIF); elifPart = myBuilder.mark(); } @@ -617,7 +624,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == elseKeyword) { myBuilder.advanceLexer(); - parseColonAndSuite(scope); + parseColonAndSuite(); elsePart.done(PyElementTypes.ELSE_PART); } else { @@ -647,33 +654,32 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { return result; } - private void parseForStatement(ParsingScope scope) { + private void parseForStatement(PsiBuilder.Marker endMarker) { assertCurrentToken(PyTokenTypes.FOR_KEYWORD); - final PsiBuilder.Marker statement = myBuilder.mark(); - parseForPart(scope); + parseForPart(); final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); - parseColonAndSuite(scope); + parseColonAndSuite(); elsePart.done(PyElementTypes.ELSE_PART); } else { elsePart.drop(); } - statement.done(PyElementTypes.FOR_STATEMENT); + endMarker.done(PyElementTypes.FOR_STATEMENT); } - protected void parseForPart(ParsingScope scope) { + protected void parseForPart() { final PsiBuilder.Marker forPart = myBuilder.mark(); myBuilder.advanceLexer(); getExpressionParser().parseExpression(true, true); checkMatches(PyTokenTypes.IN_KEYWORD, "'in' expected"); getExpressionParser().parseExpression(); - parseColonAndSuite(scope); + parseColonAndSuite(); forPart.done(PyElementTypes.FOR_PART); } - private void parseWhileStatement(ParsingScope scope) { + private void parseWhileStatement() { assertCurrentToken(PyTokenTypes.WHILE_KEYWORD); final PsiBuilder.Marker statement = myBuilder.mark(); final PsiBuilder.Marker whilePart = myBuilder.mark(); @@ -681,12 +687,12 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error(EXPRESSION_EXPECTED); } - parseColonAndSuite(scope); + parseColonAndSuite(); whilePart.done(PyElementTypes.WHILE_PART); final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); - parseColonAndSuite(scope); + parseColonAndSuite(); elsePart.done(PyElementTypes.ELSE_PART); } else { @@ -695,12 +701,12 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { statement.done(PyElementTypes.WHILE_STATEMENT); } - private void parseTryStatement(ParsingScope scope) { + private void parseTryStatement() { assertCurrentToken(PyTokenTypes.TRY_KEYWORD); final PsiBuilder.Marker statement = myBuilder.mark(); final PsiBuilder.Marker tryPart = myBuilder.mark(); myBuilder.advanceLexer(); - parseColonAndSuite(scope); + parseColonAndSuite(); tryPart.done(PyElementTypes.TRY_PART); boolean haveExceptClause = false; if (myBuilder.getTokenType() == PyTokenTypes.EXCEPT_KEYWORD) { @@ -720,13 +726,13 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } } } - parseColonAndSuite(scope); + parseColonAndSuite(); exceptBlock.done(PyElementTypes.EXCEPT_PART); } final PsiBuilder.Marker elsePart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.ELSE_KEYWORD) { myBuilder.advanceLexer(); - parseColonAndSuite(scope); + parseColonAndSuite(); elsePart.done(PyElementTypes.ELSE_PART); } else { @@ -736,7 +742,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker finallyPart = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.FINALLY_KEYWORD) { myBuilder.advanceLexer(); - parseColonAndSuite(scope); + parseColonAndSuite(); finallyPart.done(PyElementTypes.FINALLY_PART); } else { @@ -750,9 +756,9 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { statement.done(PyElementTypes.TRY_EXCEPT_STATEMENT); } - private void parseColonAndSuite(ParsingScope scope) { + private void parseColonAndSuite() { if (expectColon()) { - parseSuite(scope); + parseSuite(); } else { final PsiBuilder.Marker mark = myBuilder.mark(); @@ -760,9 +766,8 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } } - private void parseWithStatement(ParsingScope scope) { + private void parseWithStatement(PsiBuilder.Marker endMarker) { assertCurrentToken(PyTokenTypes.WITH_KEYWORD); - final PsiBuilder.Marker statement = myBuilder.mark(); myBuilder.advanceLexer(); while (true) { PsiBuilder.Marker withItem = myBuilder.mark(); @@ -780,16 +785,16 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { break; } } - parseColonAndSuite(scope); - statement.done(PyElementTypes.WITH_STATEMENT); + parseColonAndSuite(); + endMarker.done(PyElementTypes.WITH_STATEMENT); } - private void parseClassDeclaration(ParsingScope scope) { + private void parseClassDeclaration() { final PsiBuilder.Marker classMarker = myBuilder.mark(); - parseClassDeclaration(classMarker, scope); + parseClassDeclaration(classMarker); } - public void parseClassDeclaration(PsiBuilder.Marker classMarker, ParsingScope scope) { + public void parseClassDeclaration(PsiBuilder.Marker classMarker) { assertCurrentToken(PyTokenTypes.CLASS_KEYWORD); myBuilder.advanceLexer(); parseIdentifierOrSkip(PyTokenTypes.LPAR, PyTokenTypes.COLON); @@ -800,15 +805,41 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker inheritMarker = myBuilder.mark(); inheritMarker.done(PyElementTypes.ARGUMENT_LIST); } - parseColonAndSuite(scope.withClass(true)); + final ParsingContext context = getParsingContext(); + context.pushScope(context.getScope().withClass(true)); + parseColonAndSuite(); + context.popScope(); classMarker.done(PyElementTypes.CLASS_DECLARATION); } - public void parseSuite(ParsingScope scope) { - parseSuite(null, null, scope); + private void parseAsyncStatement() { + assertCurrentToken(PyTokenTypes.ASYNC_KEYWORD); + final PsiBuilder.Marker marker = myBuilder.mark(); + myBuilder.advanceLexer(); + final IElementType token = myBuilder.getTokenType(); + if (token == PyTokenTypes.DEF_KEYWORD) { + final ParsingContext context = getParsingContext(); + context.pushScope(context.getScope().withAsync()); + getFunctionParser().parseFunctionDeclaration(marker); + context.popScope(); + } + else if (token == PyTokenTypes.WITH_KEYWORD) { + parseWithStatement(marker); + } + else if (token == PyTokenTypes.FOR_KEYWORD) { + parseForStatement(marker); + } + else { + marker.drop(); + myBuilder.error("'def' or 'with' or 'for' expected"); + } } - public void parseSuite(@Nullable PsiBuilder.Marker endMarker, @Nullable IElementType elType, ParsingScope scope) { + public void parseSuite() { + parseSuite(null, null); + } + + public void parseSuite(@Nullable PsiBuilder.Marker endMarker, @Nullable IElementType elType) { if (myBuilder.getTokenType() == PyTokenTypes.STATEMENT_BREAK) { myBuilder.advanceLexer(); @@ -821,7 +852,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { } else { while (!myBuilder.eof() && myBuilder.getTokenType() != PyTokenTypes.DEDENT) { - parseStatement(scope); + parseStatement(); } } } @@ -837,10 +868,6 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (indentFound && !myBuilder.eof()) { checkMatches(PyTokenTypes.DEDENT, "Dedent expected"); } - // NOTE: the following line advances the PsiBuilder lexer and thus - // ensures that the whitespace following the statement list is included - // in the block containing the statement list - myBuilder.getTokenType(); } else { final PsiBuilder.Marker marker = myBuilder.mark(); @@ -848,12 +875,17 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { myBuilder.error("Statement expected"); } else { - parseSimpleStatement(scope.withSuite(true)); + final ParsingContext context = getParsingContext(); + context.pushScope(context.getScope().withSuite(true)); + parseSimpleStatement(); + context.popScope(); while (matchToken(PyTokenTypes.SEMICOLON)) { if (matchToken(PyTokenTypes.STATEMENT_BREAK)) { break; } - parseSimpleStatement(scope.withSuite(true)); + context.pushScope(context.getScope().withSuite(true)); + parseSimpleStatement(); + context.popScope(); } } marker.done(PyElementTypes.STATEMENT_LIST); @@ -905,6 +937,11 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (isWordAtPosition(text, start, end, TOK_NONLOCAL)) { return PyTokenTypes.NONLOCAL_KEYWORD; } + if (myContext.getLanguageLevel().isAtLeast(LanguageLevel.PYTHON35) && isWordAtPosition(text, start, end, TOK_ASYNC)) { + if (myContext.getScope().isAsync() || myBuilder.lookAhead(1) == PyTokenTypes.DEF_KEYWORD) { + return PyTokenTypes.ASYNC_KEYWORD; + } + } } else if (!myContext.getLanguageLevel().isPy3K() && source == PyTokenTypes.IDENTIFIER) { if (isWordAtPosition(text, start, end, TOK_EXEC)) { diff --git a/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java b/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java new file mode 100644 index 000000000000..717d59f8e37d --- /dev/null +++ b/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.jetbrains.python.validation; + +import com.intellij.codeInsight.daemon.impl.HighlightRangeExtension; +import com.intellij.lang.ASTNode; +import com.intellij.lang.annotation.Annotation; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.jetbrains.python.PyTokenTypes; +import com.jetbrains.python.highlighting.PyHighlighter; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.PyForStatement; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyWithStatement; +import org.jetbrains.annotations.NotNull; + +/** + * @author vlan + */ +public class DumbAwareHighlightingAnnotator extends PyAnnotator implements HighlightRangeExtension { + @Override + public void visitPyFunction(PyFunction node) { + highlightAsyncKeyword(node); + } + + @Override + public void visitPyForStatement(PyForStatement node) { + highlightAsyncKeyword(node); + } + + @Override + public void visitPyWithStatement(PyWithStatement node) { + highlightAsyncKeyword(node); + } + + @Override + public boolean isForceHighlightParents(@NotNull PsiFile file) { + return file instanceof PyFile; + } + + private void highlightAsyncKeyword(@NotNull PsiElement node) { + final ASTNode asyncNode = node.getNode().findChildByType(PyTokenTypes.ASYNC_KEYWORD); + if (asyncNode != null) { + final Annotation annotation = getHolder().createInfoAnnotation(asyncNode, null); + annotation.setTextAttributes(PyHighlighter.PY_KEYWORD); + } + } +} diff --git a/python/testData/highlighting/async.py b/python/testData/highlighting/async.py new file mode 100644 index 000000000000..545f5ebf2c91 --- /dev/null +++ b/python/testData/highlighting/async.py @@ -0,0 +1,28 @@ +async def foo(): + pass + +async = 1 + +async def bar(): + pass + + +async def # Incomplete + + +def regular(xs): + + async def quux(): + async for x in xs: + pass + + async with xs: + pass + + async for x in xs: + pass + + async with xs: + pass + + return async diff --git a/python/testData/highlighting/returnOutsideOfFunction.py b/python/testData/highlighting/returnOutsideOfFunction.py index 8ce3bb17b73d..78054559568a 100644 --- a/python/testData/highlighting/returnOutsideOfFunction.py +++ b/python/testData/highlighting/returnOutsideOfFunction.py @@ -1,3 +1,3 @@ -def foo(): - class C: +def foo(): + class C: return 1 \ No newline at end of file diff --git a/python/testData/highlighting/returnWithArgumentsInGenerator.py b/python/testData/highlighting/returnWithArgumentsInGenerator.py index f16d817498ed..4c5ba91e5aba 100644 --- a/python/testData/highlighting/returnWithArgumentsInGenerator.py +++ b/python/testData/highlighting/returnWithArgumentsInGenerator.py @@ -1,3 +1,3 @@ -def f(): +def f(): yield 42 return 28 diff --git a/python/testData/psi/AsyncDef.py b/python/testData/psi/AsyncDef.py new file mode 100644 index 000000000000..4a7b81c00582 --- /dev/null +++ b/python/testData/psi/AsyncDef.py @@ -0,0 +1,15 @@ +async def foo(x, y): + pass + + async def foo_nested(): + pass + + +async = 10 + + +def bar(): + print(async) + + async def bar_nested(): + pass diff --git a/python/testData/psi/AsyncDef.txt b/python/testData/psi/AsyncDef.txt new file mode 100644 index 000000000000..15792ae90d92 --- /dev/null +++ b/python/testData/psi/AsyncDef.txt @@ -0,0 +1,80 @@ +PyFile:AsyncDef.py + PyFunction('foo') + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('foo') + PyParameterList + PsiElement(Py:LPAR)('(') + PyNamedParameter('x') + PsiElement(Py:IDENTIFIER)('x') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyNamedParameter('y') + PsiElement(Py:IDENTIFIER)('y') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n ') + PyFunction('foo_nested') + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('foo_nested') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n\n') + PyAssignmentStatement + PyTargetExpression: async + PsiElement(Py:IDENTIFIER)('async') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('10') + PsiWhiteSpace('\n\n\n') + PyFunction('bar') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('bar') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyCallExpression: print + PyReferenceExpression: print + PsiElement(Py:IDENTIFIER)('print') + PyArgumentList + PsiElement(Py:LPAR)('(') + PyReferenceExpression: async + PsiElement(Py:IDENTIFIER)('async') + PsiElement(Py:RPAR)(')') + PsiWhiteSpace('\n\n ') + PyFunction('bar_nested') + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('bar_nested') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') \ No newline at end of file diff --git a/python/testData/psi/AsyncFor.py b/python/testData/psi/AsyncFor.py new file mode 100644 index 000000000000..2eb8f7abe936 --- /dev/null +++ b/python/testData/psi/AsyncFor.py @@ -0,0 +1,7 @@ +async def f(): + async for x in xs: + pass + + +async for y in ys: + pass diff --git a/python/testData/psi/AsyncFor.txt b/python/testData/psi/AsyncFor.txt new file mode 100644 index 000000000000..abadd01f0402 --- /dev/null +++ b/python/testData/psi/AsyncFor.txt @@ -0,0 +1,54 @@ +PyFile:AsyncFor.py + PyFunction('f') + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('f') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyForStatement + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PyForPart + PsiElement(Py:FOR_KEYWORD)('for') + PsiWhiteSpace(' ') + PyTargetExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiWhiteSpace(' ') + PsiElement(Py:IN_KEYWORD)('in') + PsiWhiteSpace(' ') + PyReferenceExpression: xs + PsiElement(Py:IDENTIFIER)('xs') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n\n') + PyExpressionStatement + PyReferenceExpression: async + PsiElement(Py:IDENTIFIER)('async') + PsiErrorElement:End of statement expected + + PsiWhiteSpace(' ') + PyForStatement + PyForPart + PsiElement(Py:FOR_KEYWORD)('for') + PsiWhiteSpace(' ') + PyTargetExpression: y + PsiElement(Py:IDENTIFIER)('y') + PsiWhiteSpace(' ') + PsiElement(Py:IN_KEYWORD)('in') + PsiWhiteSpace(' ') + PyReferenceExpression: ys + PsiElement(Py:IDENTIFIER)('ys') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') \ No newline at end of file diff --git a/python/testData/psi/AsyncWith.py b/python/testData/psi/AsyncWith.py new file mode 100644 index 000000000000..6409856e4ce6 --- /dev/null +++ b/python/testData/psi/AsyncWith.py @@ -0,0 +1,6 @@ +async def foo(): + async with x: + pass + +async with y: + pass diff --git a/python/testData/psi/AsyncWith.txt b/python/testData/psi/AsyncWith.txt new file mode 100644 index 000000000000..0313005b086d --- /dev/null +++ b/python/testData/psi/AsyncWith.txt @@ -0,0 +1,44 @@ +PyFile:AsyncWith.py + PyFunction('foo') + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('foo') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyWithStatement + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:WITH_KEYWORD)('with') + PsiWhiteSpace(' ') + PyWithItem + PyReferenceExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n\n') + PyExpressionStatement + PyReferenceExpression: async + PsiElement(Py:IDENTIFIER)('async') + PsiErrorElement:End of statement expected + + PsiWhiteSpace(' ') + PyWithStatement + PsiElement(Py:WITH_KEYWORD)('with') + PsiWhiteSpace(' ') + PyWithItem + PyReferenceExpression: y + PsiElement(Py:IDENTIFIER)('y') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java index 73ab49e28659..83e625b7d3bc 100644 --- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java @@ -229,6 +229,10 @@ public class PythonHighlightingTest extends PyTestCase { doTest(); } + public void testAsync() { + doTest(LanguageLevel.PYTHON35, true, true); + } + // --- private void doTest(final LanguageLevel languageLevel, final boolean checkWarnings, final boolean checkInfos) { PythonLanguageLevelPusher.setForcedLanguageLevel(myFixture.getProject(), languageLevel); diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index 56a4c0523f64..ecf2c39f46f3 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -486,6 +486,18 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } + public void testAsyncDef() { + doTest(LanguageLevel.PYTHON35); + } + + public void testAsyncWith() { + doTest(LanguageLevel.PYTHON35); + } + + public void testAsyncFor() { + doTest(LanguageLevel.PYTHON35); + } + public void doTest(LanguageLevel languageLevel) { LanguageLevel prev = myLanguageLevel; myLanguageLevel = languageLevel;