diff --git a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java index be858df92181..3e5c2037de96 100644 --- a/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java +++ b/python/psi-api/src/com/jetbrains/python/PyTokenTypes.java @@ -74,6 +74,7 @@ public class PyTokenTypes { 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 AWAIT_KEYWORD = new PyElementType("AWAIT_KEYWORD", "__await__"); 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/com/jetbrains/python/PyElementTypes.java b/python/src/com/jetbrains/python/PyElementTypes.java index 400e21b178c7..7d104bafb6af 100644 --- a/python/src/com/jetbrains/python/PyElementTypes.java +++ b/python/src/com/jetbrains/python/PyElementTypes.java @@ -130,7 +130,8 @@ public interface PyElementTypes { PyTokenTypes.MINUS, PyTokenTypes.MULT, PyTokenTypes.AT, PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC, PyTokenTypes.EXP); - TokenSet UNARY_OPS = TokenSet.create(PyTokenTypes.NOT_KEYWORD, PyTokenTypes.PLUS, PyTokenTypes.MINUS, PyTokenTypes.TILDE); + TokenSet UNARY_OPS = TokenSet.create(PyTokenTypes.NOT_KEYWORD, PyTokenTypes.PLUS, PyTokenTypes.MINUS, PyTokenTypes.TILDE, + PyTokenTypes.AWAIT_KEYWORD); // Parts PyElementType IF_PART_IF = new PyElementType("IF_IF", PyIfPartIfImpl.class); diff --git a/python/src/com/jetbrains/python/PythonTokenSetContributor.java b/python/src/com/jetbrains/python/PythonTokenSetContributor.java index 51a42589109b..ac3d95c17b30 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, ASYNC_KEYWORD); + NONE_KEYWORD, TRUE_KEYWORD, FALSE_KEYWORD, NONLOCAL_KEYWORD, DEBUG_KEYWORD, ASYNC_KEYWORD, AWAIT_KEYWORD); } @NotNull diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index 850f3fec2b8c..616c3d954449 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -915,7 +915,7 @@ public class ExpressionParsing extends Parsing { private boolean parsePowerExpression(boolean isTargetExpression) { PsiBuilder.Marker expr = myBuilder.mark(); - if (!parseMemberExpression(isTargetExpression)) { + if (!parseAwaitExpression(isTargetExpression)) { expr.drop(); return false; } @@ -933,4 +933,19 @@ public class ExpressionParsing extends Parsing { return true; } + + private boolean parseAwaitExpression(boolean isTargetExpression) { + if (atToken(PyTokenTypes.AWAIT_KEYWORD)) { + PsiBuilder.Marker expr = myBuilder.mark(); + myBuilder.advanceLexer(); + if (!parseMemberExpression(isTargetExpression)) { + myBuilder.error(message("PARSE.expected.expression")); + } + expr.done(PyElementTypes.PREFIX_EXPRESSION); + return true; + } + else { + return parseMemberExpression(isTargetExpression); + } + } } diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index bd6b423c033e..e5c7124c7a5d 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -51,6 +51,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { @NonNls protected static final String TOK_NONLOCAL = "nonlocal"; @NonNls protected static final String TOK_EXEC = "exec"; @NonNls protected static final String TOK_ASYNC = "async"; + @NonNls protected static final String TOK_AWAIT = "await"; private static final String EXPRESSION_EXPECTED = "Expression expected"; public static final String IDENTIFIER_EXPECTED = "Identifier expected"; @@ -937,9 +938,16 @@ 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; + if (myContext.getLanguageLevel().isAtLeast(LanguageLevel.PYTHON35)) { + if (isWordAtPosition(text, start, end, TOK_ASYNC)) { + if (myContext.getScope().isAsync() || myBuilder.lookAhead(1) == PyTokenTypes.DEF_KEYWORD) { + return PyTokenTypes.ASYNC_KEYWORD; + } + } + if (isWordAtPosition(text, start, end, TOK_AWAIT)) { + if (myContext.getScope().isAsync()) { + return PyTokenTypes.AWAIT_KEYWORD; + } } } } diff --git a/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java b/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java index 717d59f8e37d..b90f8697c6a2 100644 --- a/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java +++ b/python/src/com/jetbrains/python/validation/DumbAwareHighlightingAnnotator.java @@ -22,10 +22,7 @@ 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 com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; /** @@ -34,17 +31,22 @@ import org.jetbrains.annotations.NotNull; public class DumbAwareHighlightingAnnotator extends PyAnnotator implements HighlightRangeExtension { @Override public void visitPyFunction(PyFunction node) { - highlightAsyncKeyword(node); + highlightKeyword(node, PyTokenTypes.ASYNC_KEYWORD); } @Override public void visitPyForStatement(PyForStatement node) { - highlightAsyncKeyword(node); + highlightKeyword(node, PyTokenTypes.ASYNC_KEYWORD); } @Override public void visitPyWithStatement(PyWithStatement node) { - highlightAsyncKeyword(node); + highlightKeyword(node, PyTokenTypes.ASYNC_KEYWORD); + } + + @Override + public void visitPyPrefixExpression(PyPrefixExpression node) { + highlightKeyword(node, PyTokenTypes.AWAIT_KEYWORD); } @Override @@ -52,8 +54,8 @@ public class DumbAwareHighlightingAnnotator extends PyAnnotator implements Highl return file instanceof PyFile; } - private void highlightAsyncKeyword(@NotNull PsiElement node) { - final ASTNode asyncNode = node.getNode().findChildByType(PyTokenTypes.ASYNC_KEYWORD); + private void highlightKeyword(@NotNull PsiElement node, @NotNull PyElementType elementType) { + final ASTNode asyncNode = node.getNode().findChildByType(elementType); if (asyncNode != null) { final Annotation annotation = getHolder().createInfoAnnotation(asyncNode, null); annotation.setTextAttributes(PyHighlighter.PY_KEYWORD); diff --git a/python/testData/highlighting/await.py b/python/testData/highlighting/await.py new file mode 100644 index 000000000000..ec8e80b683b2 --- /dev/null +++ b/python/testData/highlighting/await.py @@ -0,0 +1,5 @@ +async def foo(): + await x + + +await = 0 diff --git a/python/testData/psi/Await.py b/python/testData/psi/Await.py new file mode 100644 index 000000000000..58bfdcaaa1dd --- /dev/null +++ b/python/testData/psi/Await.py @@ -0,0 +1,5 @@ +async def f(x): + await x + + +await = 1 diff --git a/python/testData/psi/Await.txt b/python/testData/psi/Await.txt new file mode 100644 index 000000000000..9b5ee093ceb9 --- /dev/null +++ b/python/testData/psi/Await.txt @@ -0,0 +1,30 @@ +PyFile:Await.py + PyFunction('f') + PsiElement(Py:ASYNC_KEYWORD)('async') + PsiWhiteSpace(' ') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('f') + PyParameterList + PsiElement(Py:LPAR)('(') + PyNamedParameter('x') + PsiElement(Py:IDENTIFIER)('x') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyPrefixExpression + PsiElement(Py:AWAIT_KEYWORD)('await') + PsiWhiteSpace(' ') + PyReferenceExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiWhiteSpace('\n\n\n') + PyAssignmentStatement + PyTargetExpression: await + PsiElement(Py:IDENTIFIER)('await') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('1') \ 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 83e625b7d3bc..3095bda21611 100644 --- a/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonHighlightingTest.java @@ -233,6 +233,10 @@ public class PythonHighlightingTest extends PyTestCase { doTest(LanguageLevel.PYTHON35, true, true); } + public void testAwait() { + 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 ecf2c39f46f3..99172dce2e48 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -498,6 +498,10 @@ public class PythonParsingTest extends ParsingTestCase { doTest(LanguageLevel.PYTHON35); } + public void testAwait() { + doTest(LanguageLevel.PYTHON35); + } + public void doTest(LanguageLevel languageLevel) { LanguageLevel prev = myLanguageLevel; myLanguageLevel = languageLevel;