diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index 5bd0b6b868f0..4374ac9d4d75 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -228,7 +228,7 @@ public class ExpressionParsing extends Parsing { expr = expr.precede(); } else if (tokenType == PyTokenTypes.LPAR) { - parseArgumentList(builder); + parseArgumentList(); expr.done(PyElementTypes.CALL_EXPRESSION); expr = expr.precede(); } @@ -297,48 +297,48 @@ public class ExpressionParsing extends Parsing { expr.done(PyElementTypes.SLICE_EXPRESSION); } - public void parseArgumentList(final PsiBuilder builder) { - LOG.assertTrue(builder.getTokenType() == PyTokenTypes.LPAR); - final PsiBuilder.Marker arglist = builder.mark(); - final PsiBuilder.Marker genexpr = builder.mark(); - builder.advanceLexer(); + public void parseArgumentList() { + LOG.assertTrue(myBuilder.getTokenType() == PyTokenTypes.LPAR); + final PsiBuilder.Marker arglist = myBuilder.mark(); + final PsiBuilder.Marker genexpr = myBuilder.mark(); + myBuilder.advanceLexer(); int argNumber = 0; boolean needBracket = true; - while (builder.getTokenType() != PyTokenTypes.RPAR) { + while (myBuilder.getTokenType() != PyTokenTypes.RPAR) { argNumber++; if (argNumber > 1) { - if (argNumber == 2 && builder.getTokenType() == PyTokenTypes.FOR_KEYWORD) { + if (argNumber == 2 && myBuilder.getTokenType() == PyTokenTypes.FOR_KEYWORD && genexpr != null) { parseListCompExpression(genexpr, PyTokenTypes.RPAR, PyElementTypes.GENERATOR_EXPRESSION); needBracket = false; break; } - else if (builder.getTokenType() == PyTokenTypes.COMMA) { - builder.advanceLexer(); - if (builder.getTokenType() == PyTokenTypes.RPAR) { + else if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { + myBuilder.advanceLexer(); + if (myBuilder.getTokenType() == PyTokenTypes.RPAR) { break; } } else { - builder.error(message("PARSE.expected.comma.or.rpar")); + myBuilder.error(message("PARSE.expected.comma.or.rpar")); break; } } - if (builder.getTokenType() == PyTokenTypes.MULT || builder.getTokenType() == PyTokenTypes.EXP) { - final PsiBuilder.Marker starArgMarker = builder.mark(); - builder.advanceLexer(); + if (myBuilder.getTokenType() == PyTokenTypes.MULT || myBuilder.getTokenType() == PyTokenTypes.EXP) { + final PsiBuilder.Marker starArgMarker = myBuilder.mark(); + myBuilder.advanceLexer(); if (!parseSingleExpression(false)) { - builder.error(message("PARSE.expected.expression")); + myBuilder.error(message("PARSE.expected.expression")); } starArgMarker.done(PyElementTypes.STAR_ARGUMENT_EXPRESSION); } else { - if (builder.getTokenType() == PyTokenTypes.IDENTIFIER) { - final PsiBuilder.Marker keywordArgMarker = builder.mark(); - builder.advanceLexer(); - if (builder.getTokenType() == PyTokenTypes.EQ) { - builder.advanceLexer(); + if (myBuilder.getTokenType() == PyTokenTypes.IDENTIFIER) { + final PsiBuilder.Marker keywordArgMarker = myBuilder.mark(); + myBuilder.advanceLexer(); + if (myBuilder.getTokenType() == PyTokenTypes.EQ) { + myBuilder.advanceLexer(); if (!parseSingleExpression(false)) { - builder.error(message("PARSE.expected.expression")); + myBuilder.error(message("PARSE.expected.expression")); } keywordArgMarker.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); continue; @@ -346,13 +346,15 @@ public class ExpressionParsing extends Parsing { keywordArgMarker.rollbackTo(); } if (!parseSingleExpression(false)) { - builder.error(message("PARSE.expected.expression")); + myBuilder.error(message("PARSE.expected.expression")); } } } if (needBracket) { - genexpr.drop(); + if (genexpr != null) { + genexpr.drop(); + } checkMatches(PyTokenTypes.RPAR, message("PARSE.expected.rpar")); } arglist.done(PyElementTypes.ARGUMENT_LIST); diff --git a/python/src/com/jetbrains/python/parsing/FunctionParsing.java b/python/src/com/jetbrains/python/parsing/FunctionParsing.java index e94f5bf4b4ff..98bba21d2b17 100644 --- a/python/src/com/jetbrains/python/parsing/FunctionParsing.java +++ b/python/src/com/jetbrains/python/parsing/FunctionParsing.java @@ -48,7 +48,7 @@ public class FunctionParsing extends Parsing { myBuilder.advanceLexer(); getStatementParser().parseDottedName(); if (myBuilder.getTokenType() == PyTokenTypes.LPAR) { - getExpressionParser().parseArgumentList(myBuilder); + getExpressionParser().parseArgumentList(); } else { // empty arglist node, so we always have it myBuilder.mark().done(PyElementTypes.ARGUMENT_LIST); diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index 2de5ee1c7cef..a8a42d340f65 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -665,15 +665,13 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { assertCurrentToken(PyTokenTypes.CLASS_KEYWORD); myBuilder.advanceLexer(); checkMatches(PyTokenTypes.IDENTIFIER, "identifier expected"); - final PsiBuilder.Marker inheritMarker = myBuilder.mark(); if (myBuilder.getTokenType() == PyTokenTypes.LPAR) { - myBuilder.advanceLexer(); - if (myBuilder.getTokenType() != PyTokenTypes.RPAR) { - getExpressionParser().parseExpression(); - } - checkMatches(PyTokenTypes.RPAR, ") expected"); + getExpressionParser().parseArgumentList(); + } + else { + final PsiBuilder.Marker inheritMarker = myBuilder.mark(); + inheritMarker.done(PyElementTypes.ARGUMENT_LIST); } - inheritMarker.done(PyElementTypes.PARENTHESIZED_EXPRESSION); checkMatches(PyTokenTypes.COLON, "colon expected"); parseSuite(); classMarker.done(PyElementTypes.CLASS_DECLARATION); diff --git a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java index 4e5b2e36ac80..280207bcfc3b 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyClassImpl.java @@ -81,15 +81,9 @@ public class PyClassImpl extends PyPresentableElementImpl implement @NotNull public PyExpression[] getSuperClassExpressions() { - final PyParenthesizedExpression superExpression = PsiTreeUtil.getChildOfType(this, PyParenthesizedExpression.class); - if (superExpression != null) { - PyExpression expr = superExpression.getContainedExpression(); - if (expr instanceof PyTupleExpression) { - return ((PyTupleExpression) expr).getElements(); - } - if (expr != null) { - return new PyExpression[] { expr }; - } + final PyArgumentList argList = PsiTreeUtil.getChildOfType(this, PyArgumentList.class); + if (argList != null) { + return argList.getArguments(); } return PyExpression.EMPTY_ARRAY; } @@ -181,7 +175,7 @@ public class PyClassImpl extends PyPresentableElementImpl implement List result = new ArrayList(); // maybe a bare old-style class? // TODO: depend on language version: py3k does not do old style classes - PsiElement paren = PsiTreeUtil.getChildOfType(this, PyParenthesizedExpression.class).getFirstChild(); // no NPE, we always have the par expr + PsiElement paren = PsiTreeUtil.getChildOfType(this, PyArgumentList.class).getFirstChild(); // no NPE, we always have the par expr if (paren != null && "(".equals(paren.getText())) { // "()" after class name, it's new style for(PsiElement element: superClassElements) { if (element instanceof PyClass) { diff --git a/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java b/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java index 4fdb69ef65e5..1baa3c8cc68b 100644 --- a/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java +++ b/python/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java @@ -44,7 +44,7 @@ public class PythonLanguageLevelPusher implements FilePropertyPusher PsiElement(Py:COLON)(':') PsiWhiteSpace('\n ') diff --git a/python/testData/psi/EmptySuperclassList.txt b/python/testData/psi/EmptySuperclassList.txt index 29bae12e0409..68ba28aed240 100644 --- a/python/testData/psi/EmptySuperclassList.txt +++ b/python/testData/psi/EmptySuperclassList.txt @@ -3,7 +3,7 @@ PyFile:EmptySuperclassList.py PsiElement(Py:CLASS_KEYWORD)('class') PsiWhiteSpace(' ') PsiElement(Py:IDENTIFIER)('A') - PyParenthesizedExpression + PyArgumentList PsiElement(Py:LPAR)('(') PsiElement(Py:RPAR)(')') PsiElement(Py:COLON)(':') diff --git a/python/testData/psi/SuperclassKeywordArguments.py b/python/testData/psi/SuperclassKeywordArguments.py new file mode 100644 index 000000000000..beb7bd88228c --- /dev/null +++ b/python/testData/psi/SuperclassKeywordArguments.py @@ -0,0 +1,3 @@ +class Foo(base1, base2, metaclass=mymeta): pass +class Bar(*bases, **kwds): pass + diff --git a/python/testData/psi/SuperclassKeywordArguments.txt b/python/testData/psi/SuperclassKeywordArguments.txt new file mode 100644 index 000000000000..664a13f2345e --- /dev/null +++ b/python/testData/psi/SuperclassKeywordArguments.txt @@ -0,0 +1,49 @@ +PyFile:SuperclassKeywordArguments.py + PyClass: Foo + PsiElement(Py:CLASS_KEYWORD)('class') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('Foo') + PyArgumentList + PsiElement(Py:LPAR)('(') + PyReferenceExpression: base1 + PsiElement(Py:IDENTIFIER)('base1') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyReferenceExpression: base2 + PsiElement(Py:IDENTIFIER)('base2') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyKeywordArgumentImpl: metaclass + PsiElement(Py:IDENTIFIER)('metaclass') + PsiElement(Py:EQ)('=') + PyReferenceExpression: mymeta + PsiElement(Py:IDENTIFIER)('mymeta') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace(' ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') + PsiWhiteSpace('\n') + PyClass: Bar + PsiElement(Py:CLASS_KEYWORD)('class') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('Bar') + PyArgumentList + PsiElement(Py:LPAR)('(') + PyStarArgument + PsiElement(Py:MULT)('*') + PyReferenceExpression: bases + PsiElement(Py:IDENTIFIER)('bases') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyStarArgument + PsiElement(Py:EXP)('**') + PyReferenceExpression: kwds + PsiElement(Py:IDENTIFIER)('kwds') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace(' ') + PyStatementList + PyPassStatement + PsiElement(Py:PASS_KEYWORD)('pass') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java index 81b970b35265..c66eb58b7e4a 100644 --- a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java +++ b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java @@ -1,5 +1,6 @@ package com.jetbrains.python; +import com.jetbrains.python.refactoring.classes.PyPullUpTest; import junit.framework.TestSuite; /** @@ -12,6 +13,7 @@ public class PythonAllTestsSuite { public static final Class[] tests = { PythonLexerTest.class, PythonParsingTest.class, + PyStringLiteralTest.class, PyIndentTest.class, PyStatementPartsTest.class, PythonHighlightingTest.class, @@ -29,7 +31,8 @@ public class PythonAllTestsSuite { PythonDemorganLawIntentionTest.class, PyQuickFixTest.class, PySelectWordTest.class, - PySurroundWithTest.class + PySurroundWithTest.class, + PyPullUpTest.class }; public static TestSuite suite() { diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index de2a6ed39ef2..6b294a303745 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -134,6 +134,10 @@ public class PythonParsingTest extends ParsingTestCase { doTest(LanguageLevel.PYTHON30); } + public void testSuperclassKeywordArguments() throws Exception { + doTest(LanguageLevel.PYTHON30); + } + public void doTest() throws Exception { doTest(LanguageLevel.PYTHON25); }