From f49320f925523f7b8dfccc3d15b7342e5ca437fc Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Sun, 17 Feb 2013 19:03:43 +0100 Subject: [PATCH 01/11] Fixed broken debug of new threads. Better fix for jython. --- python/helpers/pydev/pydevd.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py index 70c63569b1dc..ea62282d6124 100644 --- a/python/helpers/pydev/pydevd.py +++ b/python/helpers/pydev/pydevd.py @@ -136,7 +136,8 @@ class PyDBCommandThread(PyDBDaemonThread): if self.killReceived: return - threading.settrace(None) # no debugging on this thread + + self.pyDb.SetTrace(None) # no debugging on this thread try: while not self.killReceived: @@ -164,7 +165,7 @@ class PyDBCheckAliveThread(PyDBDaemonThread): self.setName('pydevd.CheckAliveThread') def OnRun(self): - pydevd_tracing.SetTrace(None) # no debugging on this thread + self.pyDb.SetTrace(None) # no debugging on this thread while True: if not self.pyDb.haveAliveThreads(): pydev_log.debug("No alive threads, finishing debug session") @@ -203,7 +204,7 @@ class NewThreadStartup: def __call__(self): global_debugger = GetGlobalDebugger() - pydevd_tracing.SetTrace(global_debugger.trace_dispatch) + global_debugger.SetTrace(global_debugger.trace_dispatch) self.original_func(*self.args, **self.kwargs) thread.NewThreadStartup = NewThreadStartup @@ -277,6 +278,7 @@ class PyDB: self._finishDebuggingSession = False self.force_post_mortem_stop = 0 self.signature_factory = None + self.SetTrace = pydevd_tracing.SetTrace #this is a dict of thread ids pointing to thread ids. Whenever a command is passed to the java end that #acknowledges that a thread was created, the thread id should be passed here -- and if at some time we do not From 6db030272cf3945f3b70e8c05e03043bbb7efd9d Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 13:28:16 +0400 Subject: [PATCH 02/11] Cleanup --- .../PyAugmentAssignmentInspection.java | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java b/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java index 6e139c1e1d80..5503bae70e43 100644 --- a/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java @@ -18,7 +18,7 @@ import org.jetbrains.annotations.Nullable; /** * User: catherine * - * Inspection to detect assignment that can be replaced with augmented assignment. + * Inspection to detect assignments that can be replaced with augmented assignments. */ public class PyAugmentAssignmentInspection extends PyInspection { @Nls @@ -43,15 +43,18 @@ public class PyAugmentAssignmentInspection extends PyInspection { @Override public void visitPyAssignmentStatement(final PyAssignmentStatement node) { - if (node.getAssignedValue() instanceof PyBinaryExpression) { + final PyExpression value = node.getAssignedValue(); + if (value instanceof PyBinaryExpression) { final PyExpression target = node.getLeftHandSideExpression(); - final PyBinaryExpression expression = (PyBinaryExpression)node.getAssignedValue(); - if (expression == null) return; + final PyBinaryExpression expression = (PyBinaryExpression)value; PyExpression leftExpression = expression.getLeftExpression(); PyExpression rightExpression = expression.getRightExpression(); - if (rightExpression instanceof PyParenthesizedExpression) + if (rightExpression instanceof PyParenthesizedExpression) { rightExpression = ((PyParenthesizedExpression)rightExpression).getContainedExpression(); - if (rightExpression == null || target == null) return; + } + if (rightExpression == null || target == null) { + return; + } boolean changedParts = false; final String targetText = target.getText(); final String rightText = rightExpression.getText(); @@ -64,31 +67,31 @@ public class PyAugmentAssignmentInspection extends PyInspection { final PyElementType op = expression.getOperator(); final TokenSet operations = TokenSet.create(PyTokenTypes.PLUS, PyTokenTypes.MINUS, PyTokenTypes.MULT, - PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC, PyTokenTypes.AND, PyTokenTypes.OR, - PyTokenTypes.XOR, PyTokenTypes.LTLT, PyTokenTypes.GTGT, PyTokenTypes.EXP); + PyTokenTypes.FLOORDIV, PyTokenTypes.DIV, PyTokenTypes.PERC, PyTokenTypes.AND, + PyTokenTypes.OR, PyTokenTypes.XOR, PyTokenTypes.LTLT, PyTokenTypes.GTGT, + PyTokenTypes.EXP); final TokenSet commutativeOperations = TokenSet.create(PyTokenTypes.PLUS, PyTokenTypes.MULT); if ((operations.contains(op) && !changedParts) || (changedParts && commutativeOperations.contains(op))) { - if ((leftExpression instanceof PyReferenceExpression || leftExpression instanceof PySubscriptionExpression)) { - if (leftExpression.getText().equals(targetText)) { - if (rightExpression instanceof PyNumericLiteralExpression) { - final AugmentedAssignmentQuickFix quickFix = new AugmentedAssignmentQuickFix(); - registerProblem(node, "Assignment can be replaced with augmented assignment", quickFix); - } - else { - final PyType type = myTypeEvalContext.getType(rightExpression); - if (type != null) { - final PyBuiltinCache cache = PyBuiltinCache.getInstance(rightExpression); - if (PyTypeChecker.match(cache.getComplexType(), type, myTypeEvalContext) || - (PyTypeChecker.match(cache.getStringType(LanguageLevel.forElement(rightExpression)), type, - myTypeEvalContext) && !changedParts)) { - registerProblem(node, "Assignment can be replaced with augmented assignment", new AugmentedAssignmentQuickFix()); - } - } + if (leftExpression instanceof PyReferenceExpression || leftExpression instanceof PySubscriptionExpression) { + final PyType type = myTypeEvalContext.getType(rightExpression); + if (type != null) { + final PyBuiltinCache cache = PyBuiltinCache.getInstance(rightExpression); + final LanguageLevel languageLevel = LanguageLevel.forElement(rightExpression); + if (isNumeric(type, cache) || (isString(type, cache, languageLevel) && !changedParts)) { + registerProblem(node, "Assignment can be replaced with augmented assignment", new AugmentedAssignmentQuickFix()); } } } } } } + + private boolean isString(PyType type, PyBuiltinCache cache, LanguageLevel level) { + return PyTypeChecker.match(cache.getStringType(level), type, myTypeEvalContext); + } + + private boolean isNumeric(PyType type, PyBuiltinCache cache) { + return PyTypeChecker.match(cache.getComplexType(), type, myTypeEvalContext); + } } } From da88f9caec5b0d0288f15b6036652a388147872a Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 13:34:23 +0400 Subject: [PATCH 03/11] Fixed augmented assignment inspection for non-commutative operations on weak union types (PY-7605) --- .../python/inspections/PyAugmentAssignmentInspection.java | 2 +- .../PyAugmentAssignmentInspection/strOrUnknownFirstArg.py | 7 +++++++ .../inspections/PyAugmentAssignmentInspectionTest.java | 5 +++++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 python/testData/inspections/PyAugmentAssignmentInspection/strOrUnknownFirstArg.py diff --git a/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java b/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java index 5503bae70e43..5762fa6f4b4a 100644 --- a/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAugmentAssignmentInspection.java @@ -74,7 +74,7 @@ public class PyAugmentAssignmentInspection extends PyInspection { if ((operations.contains(op) && !changedParts) || (changedParts && commutativeOperations.contains(op))) { if (leftExpression instanceof PyReferenceExpression || leftExpression instanceof PySubscriptionExpression) { final PyType type = myTypeEvalContext.getType(rightExpression); - if (type != null) { + if (type != null && !PyTypeChecker.isUnknown(type)) { final PyBuiltinCache cache = PyBuiltinCache.getInstance(rightExpression); final LanguageLevel languageLevel = LanguageLevel.forElement(rightExpression); if (isNumeric(type, cache) || (isString(type, cache, languageLevel) && !changedParts)) { diff --git a/python/testData/inspections/PyAugmentAssignmentInspection/strOrUnknownFirstArg.py b/python/testData/inspections/PyAugmentAssignmentInspection/strOrUnknownFirstArg.py new file mode 100644 index 000000000000..f876b7e334f2 --- /dev/null +++ b/python/testData/inspections/PyAugmentAssignmentInspection/strOrUnknownFirstArg.py @@ -0,0 +1,7 @@ +def foo(c, x, y): + if c: + z = x + else: + z = '' + y = z + y # pass + return y diff --git a/python/testSrc/com/jetbrains/python/inspections/PyAugmentAssignmentInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyAugmentAssignmentInspectionTest.java index 3ad5432d4543..fc87de23f50f 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyAugmentAssignmentInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyAugmentAssignmentInspectionTest.java @@ -47,6 +47,11 @@ public class PyAugmentAssignmentInspectionTest extends PyTestCase { doTest(); } + // PY-7605 + public void testStrOrUnknownFirstArg() { + doTest(); + } + private void doTest() { myFixture.configureByFile("inspections/PyAugmentAssignmentInspection/" + getTestName(true) + ".py"); myFixture.enableInspections(PyAugmentAssignmentInspection.class); From 8b5b707e9c96c12eb0346dc3f65522dcacb3bf54 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 14:20:29 +0400 Subject: [PATCH 04/11] Updated Python 3 compatibility checks to include Python 3.3 and future 3.x versions --- .../PyCompatibilityInspection.java | 2 +- .../validation/CompatibilityVisitor.java | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java b/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java index 3e27f14fd1c2..632525a88747 100644 --- a/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyCompatibilityInspection.java @@ -240,7 +240,7 @@ public class PyCompatibilityInspection extends PyInspection { } final String errorMessage = "This syntax available only since py3"; final boolean isPy3 = LanguageLevel.forElement(node).isPy3K(); - if (compatibleWithPy2() || !isPy3) { + if (shouldBeCompatibleWithPy2() || !isPy3) { for (final PyElement problemElement : problemElements) myHolder.registerProblem(problemElement, errorMessage, isPy3? ProblemHighlightType.GENERIC_ERROR_OR_WARNING : ProblemHighlightType.GENERIC_ERROR); diff --git a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java index 1770a1bfbde8..bd1471516bf4 100644 --- a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java +++ b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java @@ -536,12 +536,6 @@ public abstract class CompatibilityVisitor extends PyAnnotator { // do not go to nested functions } } - private boolean shouldBeCompatibleWithPy3() { - if (myVersionsToProcess.contains(LanguageLevel.PYTHON30) || myVersionsToProcess.contains(LanguageLevel.PYTHON31) - || myVersionsToProcess.contains(LanguageLevel.PYTHON32)) - return true; - return false; - } protected abstract void registerProblem(PsiElement node, String s, @Nullable LocalQuickFix localQuickFix, boolean asError); @@ -581,13 +575,26 @@ public abstract class CompatibilityVisitor extends PyAnnotator { @Override public void visitPyNonlocalStatement(final PyNonlocalStatement node) { - if (compatibleWithPy2()) { + if (shouldBeCompatibleWithPy2()) { registerProblem(node, "nonlocal keyword available only since py3", null, false); } } - protected boolean compatibleWithPy2() { - return myVersionsToProcess.contains(LanguageLevel.PYTHON24) || myVersionsToProcess.contains(LanguageLevel.PYTHON25) || - myVersionsToProcess.contains(LanguageLevel.PYTHON26) || myVersionsToProcess.contains(LanguageLevel.PYTHON27); + protected boolean shouldBeCompatibleWithPy2() { + for (LanguageLevel level : myVersionsToProcess) { + if (level.isOlderThan(LanguageLevel.PYTHON30)) { + return true; + } + } + return false; + } + + private boolean shouldBeCompatibleWithPy3() { + for (LanguageLevel level : myVersionsToProcess) { + if (level.isPy3K()) { + return true; + } + } + return false; } } From 377662303c67a37f69ecff058b30c56d463cf5b2 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 16:08:33 +0400 Subject: [PATCH 05/11] Fixed parsing ellipsis as an expression in Python 3 (PY-8257) In Python 3 ellipsis can appear anywhere, not only in sequence slicings. The parser now always passes ellipsis, it's incorrect use is detected at the unsupported features annotator or compatibility inspection levels. --- .../python/psi/PyNoneLiteralExpression.java | 1 + .../python/parsing/ExpressionParsing.java | 70 +++++++++---------- .../python/parsing/FunctionParsing.java | 8 +-- .../python/parsing/StatementParsing.java | 34 ++++----- .../psi/impl/PyNoneLiteralExpressionImpl.java | 6 ++ .../validation/CompatibilityVisitor.java | 14 ++++ .../ellipsisAsStatementPy2.py | 2 + .../ellipsisInSubscriptionPy2.py | 5 ++ python/testData/psi/EllipsisAsStatement.py | 5 -- python/testData/psi/EllipsisAsStatement.txt | 33 --------- python/testData/psi/EllipsisPython3.py | 5 ++ python/testData/psi/EllipsisPython3.txt | 50 +++++++++++++ .../jetbrains/python/PythonParsingTest.java | 9 +-- .../PyCompatibilityInspectionTest.java | 20 ++++++ 14 files changed, 164 insertions(+), 98 deletions(-) create mode 100644 python/testData/inspections/PyCompatibilityInspection/ellipsisAsStatementPy2.py create mode 100644 python/testData/inspections/PyCompatibilityInspection/ellipsisInSubscriptionPy2.py delete mode 100644 python/testData/psi/EllipsisAsStatement.py delete mode 100644 python/testData/psi/EllipsisAsStatement.txt create mode 100644 python/testData/psi/EllipsisPython3.py create mode 100644 python/testData/psi/EllipsisPython3.txt diff --git a/python/psi-api/src/com/jetbrains/python/psi/PyNoneLiteralExpression.java b/python/psi-api/src/com/jetbrains/python/psi/PyNoneLiteralExpression.java index ca83704e149d..f5a6f2ad37d5 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/PyNoneLiteralExpression.java +++ b/python/psi-api/src/com/jetbrains/python/psi/PyNoneLiteralExpression.java @@ -6,4 +6,5 @@ package com.jetbrains.python.psi; * @author yole */ public interface PyNoneLiteralExpression extends PyLiteralExpression { + boolean isEllipsis(); } diff --git a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java index e597bf45096e..74ee45358b63 100644 --- a/python/src/com/jetbrains/python/parsing/ExpressionParsing.java +++ b/python/src/com/jetbrains/python/parsing/ExpressionParsing.java @@ -72,6 +72,9 @@ public class ExpressionParsing extends Parsing { parseReprExpression(myBuilder); return true; } + else if (parseEllipsis()) { + return true; + } return false; } @@ -97,7 +100,7 @@ public class ExpressionParsing extends Parsing { expr.done(PyElementTypes.LIST_LITERAL_EXPRESSION); return; } - if (!parseSingleExpression(isTargetExpression, false)) { + if (!parseSingleExpression(isTargetExpression)) { builder.error(message("PARSE.expected.expression")); } if (builder.getTokenType() == PyTokenTypes.FOR_KEYWORD) { @@ -111,7 +114,7 @@ public class ExpressionParsing extends Parsing { if (atToken(PyTokenTypes.RBRACKET)) { break; } - if (!parseSingleExpression(isTargetExpression, false)) { + if (!parseSingleExpression(isTargetExpression)) { builder.error(message("PARSE.expected.expr.or.comma.or.bracket")); break; } @@ -154,7 +157,7 @@ public class ExpressionParsing extends Parsing { result = parseORTestExpression(false, false); } else { - result = parseTupleExpression(false, false, true, false); + result = parseTupleExpression(false, false, true); } if (!result) { myBuilder.error("expression expected"); @@ -172,7 +175,7 @@ public class ExpressionParsing extends Parsing { } final PsiBuilder.Marker firstExprMarker = myBuilder.mark(); - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { myBuilder.error("expression expected"); firstExprMarker.drop(); expr.done(PyElementTypes.DICT_LITERAL_EXPRESSION); @@ -198,7 +201,7 @@ public class ExpressionParsing extends Parsing { } private void parseDictLiteralTail(PsiBuilder.Marker startMarker, PsiBuilder.Marker firstKeyValueMarker) { - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { myBuilder.error("expression expected"); firstKeyValueMarker.done(PyElementTypes.KEY_VALUE_EXPRESSION); if (atToken(PyTokenTypes.RBRACE)) { @@ -225,12 +228,12 @@ public class ExpressionParsing extends Parsing { private boolean parseKeyValueExpression() { final PsiBuilder.Marker marker = myBuilder.mark(); - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { marker.drop(); return false; } checkMatches(PyTokenTypes.COLON, message("PARSE.expected.colon")); - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { myBuilder.error("value expression expected"); marker.drop(); return false; @@ -242,7 +245,7 @@ public class ExpressionParsing extends Parsing { private void parseSetLiteralTail(PsiBuilder.Marker startMarker) { while (myBuilder.getTokenType() != PyTokenTypes.RBRACE) { checkMatches(PyTokenTypes.COMMA, message("PARSE.expected.comma")); - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { break; } } @@ -325,7 +328,7 @@ public class ExpressionParsing extends Parsing { parseSliceEnd(expr, sliceItemStart); } else { - boolean hadExpression = parseSingleExpression(false, true); + boolean hadExpression = parseSingleExpression(false); if (atToken(PyTokenTypes.COLON)) { sliceOrTupleStart.drop(); parseSliceEnd(expr, sliceItemStart); @@ -334,7 +337,7 @@ public class ExpressionParsing extends Parsing { sliceItemStart.done(PyElementTypes.SLICE_ITEM); if (!parseSliceListTail(expr, sliceOrTupleStart)) { sliceOrTupleStart.rollbackTo(); - if (!parseTupleExpression(false, false, false, true)) { + if (!parseTupleExpression(false, false, false)) { myBuilder.error("tuple expression expected"); } checkMatches(PyTokenTypes.RBRACKET, message("PARSE.expected.rbracket")); @@ -404,13 +407,13 @@ public class ExpressionParsing extends Parsing { sliceMarker.done(PyElementTypes.EMPTY_EXPRESSION); } else { - parseSingleExpression(false, true); + parseSingleExpression(false); } if (!BRACKET_COLON_COMMA.contains(myBuilder.getTokenType())) { myBuilder.error(message("PARSE.expected.colon.or.rbracket")); } if (matchToken(PyTokenTypes.COLON)) { - parseSingleExpression(false, true); + parseSingleExpression(false); } sliceItemStart.done(PyElementTypes.SLICE_ITEM); @@ -427,12 +430,12 @@ public class ExpressionParsing extends Parsing { while (atToken(PyTokenTypes.COMMA)) { nextToken(); PsiBuilder.Marker sliceItemStart = myBuilder.mark(); - parseTestExpression(false, false, true); + parseTestExpression(false, false); if (matchToken(PyTokenTypes.COLON)) { inSlice = true; - parseTestExpression(false, false, true); + parseTestExpression(false, false); if (matchToken(PyTokenTypes.COLON)) { - parseTestExpression(false, false, true); + parseTestExpression(false, false); } } sliceItemStart.done(PyElementTypes.SLICE_ITEM); @@ -479,7 +482,7 @@ public class ExpressionParsing extends Parsing { if (myBuilder.getTokenType() == PyTokenTypes.MULT || myBuilder.getTokenType() == PyTokenTypes.EXP) { final PsiBuilder.Marker starArgMarker = myBuilder.mark(); myBuilder.advanceLexer(); - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { myBuilder.error(message("PARSE.expected.expression")); } starArgMarker.done(PyElementTypes.STAR_ARGUMENT_EXPRESSION); @@ -490,7 +493,7 @@ public class ExpressionParsing extends Parsing { myBuilder.advanceLexer(); if (myBuilder.getTokenType() == PyTokenTypes.EQ) { myBuilder.advanceLexer(); - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { myBuilder.error(message("PARSE.expected.expression")); } keywordArgMarker.done(PyElementTypes.KEYWORD_ARGUMENT_EXPRESSION); @@ -498,7 +501,7 @@ public class ExpressionParsing extends Parsing { } keywordArgMarker.rollbackTo(); } - if (!parseSingleExpression(false, false)) { + if (!parseSingleExpression(false)) { myBuilder.error(message("PARSE.expected.expression")); break; } @@ -513,11 +516,11 @@ public class ExpressionParsing extends Parsing { } public boolean parseExpressionOptional() { - return parseTupleExpression(false, false, false, false); + return parseTupleExpression(false, false, false); } public boolean parseExpressionOptional(boolean isTargetExpression) { - return parseTupleExpression(false, isTargetExpression, false, false); + return parseTupleExpression(false, isTargetExpression, false); } public void parseExpression() { @@ -527,7 +530,7 @@ public class ExpressionParsing extends Parsing { } public void parseExpression(boolean stopOnIn, boolean isTargetExpression) { - if (!parseTupleExpression(stopOnIn, isTargetExpression, false, false)) { + if (!parseTupleExpression(stopOnIn, isTargetExpression, false)) { myBuilder.error(message("PARSE.expected.expression")); } } @@ -538,7 +541,7 @@ public class ExpressionParsing extends Parsing { myBuilder.advanceLexer(); if (myBuilder.getTokenType() == PyTokenTypes.FROM_KEYWORD) { myBuilder.advanceLexer(); - final boolean parsed = parseTupleExpression(false, isTargetExpression, false, false); + final boolean parsed = parseTupleExpression(false, isTargetExpression, false); if (!parsed) { myBuilder.error(message("PARSE.expected.expression")); } @@ -546,19 +549,19 @@ public class ExpressionParsing extends Parsing { return parsed; } else { - parseTupleExpression(false, isTargetExpression, false, false); + parseTupleExpression(false, isTargetExpression, false); yieldExpr.done(PyElementTypes.YIELD_EXPRESSION); return true; } } else { - return parseTupleExpression(false, isTargetExpression, false, false); + return parseTupleExpression(false, isTargetExpression, false); } } - protected boolean parseTupleExpression(boolean stopOnIn, boolean isTargetExpression, final boolean oldTest, boolean allowEllipsis) { + protected boolean parseTupleExpression(boolean stopOnIn, boolean isTargetExpression, final boolean oldTest) { PsiBuilder.Marker expr = myBuilder.mark(); - boolean exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression, allowEllipsis); + boolean exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression); if (!exprParseResult) { expr.drop(); return false; @@ -567,7 +570,7 @@ public class ExpressionParsing extends Parsing { while (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); PsiBuilder.Marker expr2 = myBuilder.mark(); - exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression, allowEllipsis); + exprParseResult = oldTest ? parseOldTestExpression() : parseTestExpression(stopOnIn, isTargetExpression); if (!exprParseResult) { expr2.rollbackTo(); break; @@ -582,8 +585,8 @@ public class ExpressionParsing extends Parsing { return true; } - public boolean parseSingleExpression(boolean isTargetExpression, boolean allowEllipsis) { - return parseTestExpression(false, isTargetExpression, allowEllipsis); + public boolean parseSingleExpression(boolean isTargetExpression) { + return parseTestExpression(false, isTargetExpression); } public boolean parseOldExpression() { @@ -593,13 +596,10 @@ public class ExpressionParsing extends Parsing { return parseORTestExpression(false, false); } - private boolean parseTestExpression(boolean stopOnIn, boolean isTargetExpression, boolean allowEllipsis) { + private boolean parseTestExpression(boolean stopOnIn, boolean isTargetExpression) { if (myBuilder.getTokenType() == PyTokenTypes.LAMBDA_KEYWORD) { return parseLambdaExpression(false); } - if (allowEllipsis && parseEllipsis()) { - return true; - } PsiBuilder.Marker condExpr = myBuilder.mark(); if (!parseORTestExpression( stopOnIn, isTargetExpression)) { condExpr.drop(); @@ -616,7 +616,7 @@ public class ExpressionParsing extends Parsing { } else { myBuilder.advanceLexer(); - if (!parseTestExpression(stopOnIn, isTargetExpression, allowEllipsis)) { + if (!parseTestExpression(stopOnIn, isTargetExpression)) { myBuilder.error(message("PARSE.expected.expression")); } } @@ -640,7 +640,7 @@ public class ExpressionParsing extends Parsing { PsiBuilder.Marker expr = myBuilder.mark(); myBuilder.advanceLexer(); getFunctionParser().parseParameterListContents(PyTokenTypes.COLON, false, true); - boolean parseExpressionResult = oldTest ? parseOldTestExpression() : parseSingleExpression(false, false); + boolean parseExpressionResult = oldTest ? parseOldTestExpression() : parseSingleExpression(false); if (!parseExpressionResult) { myBuilder.error(message("PARSE.expected.expression")); } diff --git a/python/src/com/jetbrains/python/parsing/FunctionParsing.java b/python/src/com/jetbrains/python/parsing/FunctionParsing.java index 40ca2371334d..7bb12f969585 100644 --- a/python/src/com/jetbrains/python/parsing/FunctionParsing.java +++ b/python/src/com/jetbrains/python/parsing/FunctionParsing.java @@ -65,7 +65,7 @@ public class FunctionParsing extends Parsing { PsiBuilder.Marker maybeReturnAnnotation = myBuilder.mark(); nextToken(); if (matchToken(PyTokenTypes.GT)) { - if (!myContext.getExpressionParser().parseSingleExpression(false, false)) { + if (!myContext.getExpressionParser().parseSingleExpression(false)) { myBuilder.error(message("PARSE.expected.expression")); } maybeReturnAnnotation.done(PyElementTypes.ANNOTATION); @@ -195,13 +195,13 @@ public class FunctionParsing extends Parsing { if (!isLambda && myContext.getLanguageLevel().isPy3K() && atToken(PyTokenTypes.COLON)) { PsiBuilder.Marker annotationMarker = myBuilder.mark(); nextToken(); - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error(message("PARSE.expected.expression")); } annotationMarker.done(PyElementTypes.ANNOTATION); } if (!isStarParameter && matchToken(PyTokenTypes.EQ)) { - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { PsiBuilder.Marker invalidElements = myBuilder.mark(); while(!atAnyOfTokens(endToken, PyTokenTypes.LINE_BREAK, PyTokenTypes.COMMA, null)) { nextToken(); @@ -251,7 +251,7 @@ public class FunctionParsing extends Parsing { } if (myBuilder.getTokenType() == PyTokenTypes.EQ) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); } tuple.done(PyElementTypes.TUPLE_PARAMETER); } diff --git a/python/src/com/jetbrains/python/parsing/StatementParsing.java b/python/src/com/jetbrains/python/parsing/StatementParsing.java index 37751366a56e..9cb92b119ddc 100644 --- a/python/src/com/jetbrains/python/parsing/StatementParsing.java +++ b/python/src/com/jetbrains/python/parsing/StatementParsing.java @@ -278,18 +278,18 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { if (builder.getTokenType() == PyTokenTypes.GTGT) { final PsiBuilder.Marker target = builder.mark(); builder.advanceLexer(); - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); target.done(PyElementTypes.PRINT_TARGET); } else { - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); } while (builder.getTokenType() == PyTokenTypes.COMMA) { builder.advanceLexer(); if (getEndOfStatementsTokens().contains(builder.getTokenType())) { break; } - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); } checkEndOfStatement(scope); statement.done(PyElementTypes.PRINT_STATEMENT); @@ -317,13 +317,13 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { assertCurrentToken(PyTokenTypes.DEL_KEYWORD); final PsiBuilder.Marker delStatement = myBuilder.mark(); myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error("Expression expected"); } while (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); if (!getEndOfStatementsTokens().contains(myBuilder.getTokenType())) { - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error("Expression expected"); } } @@ -338,18 +338,18 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker raiseStatement = myBuilder.mark(); myBuilder.advanceLexer(); if (!getEndOfStatementsTokens().contains(myBuilder.getTokenType())) { - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); } } else if (myBuilder.getTokenType() == PyTokenTypes.FROM_KEYWORD) { myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error("Expression expected"); } } @@ -362,10 +362,10 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { assertCurrentToken(PyTokenTypes.ASSERT_KEYWORD); final PsiBuilder.Marker assertStatement = myBuilder.mark(); myBuilder.advanceLexer(); - if (getExpressionParser().parseSingleExpression(false, false)) { + if (getExpressionParser().parseSingleExpression(false)) { if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myContext.getBuilder().error(EXPRESSION_EXPECTED); } } @@ -562,10 +562,10 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { getExpressionParser().parseExpression(true, false); if (myBuilder.getTokenType() == PyTokenTypes.IN_KEYWORD) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); if (myBuilder.getTokenType() == PyTokenTypes.COMMA) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(false, false); + getExpressionParser().parseSingleExpression(false); } } checkEndOfStatement(inSuite); @@ -646,7 +646,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker statement = myBuilder.mark(); final PsiBuilder.Marker whilePart = myBuilder.mark(); myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error(EXPRESSION_EXPECTED); } parseColonAndSuite(scope); @@ -677,13 +677,13 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { final PsiBuilder.Marker exceptBlock = myBuilder.mark(); myBuilder.advanceLexer(); if (myBuilder.getTokenType() != PyTokenTypes.COLON) { - if (!getExpressionParser().parseSingleExpression(false, false)) { + if (!getExpressionParser().parseSingleExpression(false)) { myBuilder.error(EXPRESSION_EXPECTED); } setExpectAsKeyword(true); if (myBuilder.getTokenType() == PyTokenTypes.COMMA || myBuilder.getTokenType() == PyTokenTypes.AS_KEYWORD) { myBuilder.advanceLexer(); - if (!getExpressionParser().parseSingleExpression(true, false)) { + if (!getExpressionParser().parseSingleExpression(true)) { myBuilder.error(EXPRESSION_EXPECTED); } } @@ -738,7 +738,7 @@ public class StatementParsing extends Parsing implements ITokenTypeRemapper { setExpectAsKeyword(true); if (myBuilder.getTokenType() == PyTokenTypes.AS_KEYWORD) { myBuilder.advanceLexer(); - getExpressionParser().parseSingleExpression(true, false); // 'as' is followed by a target + getExpressionParser().parseSingleExpression(true); // 'as' is followed by a target } withItem.done(PyElementTypes.WITH_ITEM); if (!matchToken(PyTokenTypes.COMMA)) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyNoneLiteralExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyNoneLiteralExpressionImpl.java index 21b7fe494a5a..256a1c86881e 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyNoneLiteralExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyNoneLiteralExpressionImpl.java @@ -1,6 +1,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.PyElementVisitor; import com.jetbrains.python.psi.PyNoneLiteralExpression; import com.jetbrains.python.psi.types.PyNoneType; @@ -24,4 +25,9 @@ public class PyNoneLiteralExpressionImpl extends PyElementImpl implements PyNone protected void acceptPyVisitor(PyElementVisitor pyVisitor) { pyVisitor.visitPyNoneLiteralExpression(this); } + + @Override + public boolean isEllipsis() { + return getNode().findChildByType(PyTokenTypes.DOT) != null; + } } diff --git a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java index bd1471516bf4..81febf3554f2 100644 --- a/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java +++ b/python/src/com/jetbrains/python/validation/CompatibilityVisitor.java @@ -511,6 +511,20 @@ public abstract class CompatibilityVisitor extends PyAnnotator { } } + @Override + public void visitPyNoneLiteralExpression(PyNoneLiteralExpression node) { + if (shouldBeCompatibleWithPy2() && node.isEllipsis()) { + final PySubscriptionExpression subscription = PsiTreeUtil.getParentOfType(node, PySubscriptionExpression.class); + if (subscription != null && PsiTreeUtil.isAncestor(subscription.getIndexExpression(), node, false)) { + return; + } + final PySliceItem sliceItem = PsiTreeUtil.getParentOfType(node, PySliceItem.class); + if (sliceItem != null) { + return; + } + registerProblem(node, "Python versions < 3.0 do not support '...' outside of sequence slicings."); + } + } private static class YieldVisitor extends PyElementVisitor { private boolean _haveYield = false; diff --git a/python/testData/inspections/PyCompatibilityInspection/ellipsisAsStatementPy2.py b/python/testData/inspections/PyCompatibilityInspection/ellipsisAsStatementPy2.py new file mode 100644 index 000000000000..6e4f90cd7034 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/ellipsisAsStatementPy2.py @@ -0,0 +1,2 @@ +def foo(): + ... diff --git a/python/testData/inspections/PyCompatibilityInspection/ellipsisInSubscriptionPy2.py b/python/testData/inspections/PyCompatibilityInspection/ellipsisInSubscriptionPy2.py new file mode 100644 index 000000000000..e0e70df1ee91 --- /dev/null +++ b/python/testData/inspections/PyCompatibilityInspection/ellipsisInSubscriptionPy2.py @@ -0,0 +1,5 @@ +import numpy + +x = numpy.zeros((3, 4, 5)) +y = x[..., 0] # pass +y = x[..., 0, :] # pass diff --git a/python/testData/psi/EllipsisAsStatement.py b/python/testData/psi/EllipsisAsStatement.py deleted file mode 100644 index e1efdfe13f1e..000000000000 --- a/python/testData/psi/EllipsisAsStatement.py +++ /dev/null @@ -1,5 +0,0 @@ -def a(): - ... # <- highlighted as "Statement seems to have no effect" - pass - -a() \ No newline at end of file diff --git a/python/testData/psi/EllipsisAsStatement.txt b/python/testData/psi/EllipsisAsStatement.txt deleted file mode 100644 index dbe4c271bd31..000000000000 --- a/python/testData/psi/EllipsisAsStatement.txt +++ /dev/null @@ -1,33 +0,0 @@ -PyFile:EllipsisAsStatement.py - PyFunction('a') - PsiElement(Py:DEF_KEYWORD)('def') - PsiWhiteSpace(' ') - PsiElement(Py:IDENTIFIER)('a') - PyParameterList - PsiElement(Py:LPAR)('(') - PsiElement(Py:RPAR)(')') - PsiElement(Py:COLON)(':') - PsiWhiteSpace('\n ') - PyStatementList - PsiElement(Py:DOT)('.') - PsiErrorElement:Statement expected, found Py:DOT - - PsiElement(Py:DOT)('.') - PsiErrorElement:Statement expected, found Py:DOT - - PsiElement(Py:DOT)('.') - PsiErrorElement:Statement expected, found Py:DOT - - PsiWhiteSpace(' ') - PsiComment(Py:END_OF_LINE_COMMENT)('# <- highlighted as "Statement seems to have no effect"') - PsiWhiteSpace('\n ') - PyPassStatement - PsiElement(Py:PASS_KEYWORD)('pass') - PsiWhiteSpace('\n\n') - PyExpressionStatement - PyCallExpression: a - PyReferenceExpression: a - PsiElement(Py:IDENTIFIER)('a') - PyArgumentList - PsiElement(Py:LPAR)('(') - PsiElement(Py:RPAR)(')') \ No newline at end of file diff --git a/python/testData/psi/EllipsisPython3.py b/python/testData/psi/EllipsisPython3.py new file mode 100644 index 000000000000..e4d7775aa912 --- /dev/null +++ b/python/testData/psi/EllipsisPython3.py @@ -0,0 +1,5 @@ +... +x = ... +[1, 2, ...] +def f(): + ... diff --git a/python/testData/psi/EllipsisPython3.txt b/python/testData/psi/EllipsisPython3.txt new file mode 100644 index 000000000000..af6f8ea82aa2 --- /dev/null +++ b/python/testData/psi/EllipsisPython3.txt @@ -0,0 +1,50 @@ +PyFile:EllipsisPython3.py + PyExpressionStatement + PyNoneLiteralExpression + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiWhiteSpace('\n') + PyAssignmentStatement + PyTargetExpression: x + PsiElement(Py:IDENTIFIER)('x') + PsiWhiteSpace(' ') + PsiElement(Py:EQ)('=') + PsiWhiteSpace(' ') + PyNoneLiteralExpression + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiWhiteSpace('\n') + PyExpressionStatement + PyListLiteralExpression + PsiElement(Py:LBRACKET)('[') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('1') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyNumericLiteralExpression + PsiElement(Py:INTEGER_LITERAL)('2') + PsiElement(Py:COMMA)(',') + PsiWhiteSpace(' ') + PyNoneLiteralExpression + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:RBRACKET)(']') + PsiWhiteSpace('\n') + PyFunction('f') + PsiElement(Py:DEF_KEYWORD)('def') + PsiWhiteSpace(' ') + PsiElement(Py:IDENTIFIER)('f') + PyParameterList + PsiElement(Py:LPAR)('(') + PsiElement(Py:RPAR)(')') + PsiElement(Py:COLON)(':') + PsiWhiteSpace('\n ') + PyStatementList + PyExpressionStatement + PyNoneLiteralExpression + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') + PsiElement(Py:DOT)('.') \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java index 4cdd61188e8f..f338c203a2c8 100644 --- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java +++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java @@ -356,10 +356,6 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } - public void testEllipsisAsStatement() { // PY-7763 - doTest(); - } - // PY-8606 public void testEllipsisInSliceList() { doTest(); @@ -374,6 +370,11 @@ public class PythonParsingTest extends ParsingTestCase { doTest(); } + // PY-8752 + public void testEllipsisPython3() { + doTest(LanguageLevel.PYTHON33); + } + public void doTest(LanguageLevel languageLevel) { LanguageLevel prev = myLanguageLevel; myLanguageLevel = languageLevel; diff --git a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java index 0bafbd187f90..e4f263bdb89b 100644 --- a/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/PyCompatibilityInspectionTest.java @@ -2,6 +2,7 @@ package com.jetbrains.python.inspections; import com.jetbrains.python.fixtures.PyTestCase; import com.jetbrains.python.psi.LanguageLevel; +import org.jetbrains.annotations.NotNull; /** * User : catherine @@ -119,6 +120,25 @@ public class PyCompatibilityInspectionTest extends PyTestCase { doTest(); } + // PY-7763 + public void testEllipsisAsStatementPy2() { + doTest(LanguageLevel.PYTHON33); + } + + // PY-8606 + public void testEllipsisInSubscriptionPy2() { + doTest(LanguageLevel.PYTHON33); + } + + private void doTest(@NotNull LanguageLevel level) { + runWithLanguageLevel(level, new Runnable() { + @Override + public void run() { + doTest(); + } + }); + } + private void doTest() { myFixture.configureByFile("inspections/PyCompatibilityInspection/" + getTestName(true) + ".py"); myFixture.enableInspections(PyCompatibilityInspection.class); From d8708943140b0e4f804b441d1454d04362a1bc87 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 16:42:19 +0400 Subject: [PATCH 06/11] Fixed unresolved 'os.error' on Linux (PY-7650) OSError could be imported in __builtin__ from exceptions, not defined in it as a class. --- .../codeInsight/stdlib/PyStdlibModuleMembersProvider.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibModuleMembersProvider.java b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibModuleMembersProvider.java index c7b5c20782d5..94e05074b9ab 100644 --- a/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibModuleMembersProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/stdlib/PyStdlibModuleMembersProvider.java @@ -3,7 +3,6 @@ package com.jetbrains.python.codeInsight.stdlib; import com.intellij.openapi.util.SystemInfo; import com.intellij.psi.PsiElement; import com.jetbrains.python.codeInsight.PyDynamicMember; -import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.impl.PyQualifiedName; @@ -24,10 +23,10 @@ public class PyStdlibModuleMembersProvider extends PyModuleMembersProvider { if (qName.equals("os")) { final List results = new ArrayList(); PsiElement path = null; - PyClass osError = null; + PsiElement osError = null; if (module != null) { final PyBuiltinCache builtinCache = PyBuiltinCache.getInstance(module); - osError = builtinCache.getClass("OSError"); + osError = builtinCache.getByName("OSError"); final String pathModuleName = SystemInfo.isWindows ? "ntpath" : "posixpath"; path = ResolveImportUtil.resolveModuleInRoots(PyQualifiedName.fromDottedString(pathModuleName), module); From 5b471c80af2e21acd19cf71776c0f28d807a51f5 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 18:25:15 +0400 Subject: [PATCH 07/11] Split method overriding inspection tests --- .../ArgAndKwargs.py | 8 + .../ArgsKwargsAsAllowAnything.py | 28 ++++ .../ArgsKwargsOverrideArg.py | 23 +++ .../BothArgsKwargs.py | 23 +++ .../DefaultArgument.py | 8 + .../ExtraKwargs.py | 8 + .../PyMethodOverridingInspection/InitNew.py | 20 +++ .../LessArgumentsPlusDefaults.py | 8 + .../LessParametersAndKwargs.py | 8 + .../NotOverridingMethod.py | 7 + .../RequiredParameterAndKwargs.py | 9 ++ .../PyMethodOverridingInspection/test.py | 144 ------------------ .../PyMethodOverridingInspectionTest.java | 67 ++++++++ .../jetbrains/python/PythonAllTestsSuite.java | 1 + .../python/PythonInspectionsTest.java | 5 - 15 files changed, 218 insertions(+), 149 deletions(-) create mode 100644 python/testData/inspections/PyMethodOverridingInspection/ArgAndKwargs.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsAsAllowAnything.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsOverrideArg.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/BothArgsKwargs.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/DefaultArgument.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/ExtraKwargs.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/InitNew.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/LessArgumentsPlusDefaults.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/LessParametersAndKwargs.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/NotOverridingMethod.py create mode 100644 python/testData/inspections/PyMethodOverridingInspection/RequiredParameterAndKwargs.py delete mode 100644 python/testData/inspections/PyMethodOverridingInspection/test.py create mode 100644 python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java diff --git a/python/testData/inspections/PyMethodOverridingInspection/ArgAndKwargs.py b/python/testData/inspections/PyMethodOverridingInspection/ArgAndKwargs.py new file mode 100644 index 000000000000..55640981864d --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/ArgAndKwargs.py @@ -0,0 +1,8 @@ +class B: + def foo(self, **kwargs): + pass + + +class C(B): + def foo(self, arg1=None, **kwargs): # pass + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsAsAllowAnything.py b/python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsAsAllowAnything.py new file mode 100644 index 000000000000..6058ba148c06 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsAsAllowAnything.py @@ -0,0 +1,28 @@ +class B1: + def foo(self, a, b): + pass + + +class C1(B1): + def foo(self, *b): + pass + + +class B2: + def foo(self, **kwargs): + pass + + +class C2(B2): + def foo(self): + pass + + +class B3: + def foo(self, *args): + pass + + +class C3(B3): + def foo(self): + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsOverrideArg.py b/python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsOverrideArg.py new file mode 100644 index 000000000000..8cd9f0cf9d3c --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/ArgsKwargsOverrideArg.py @@ -0,0 +1,23 @@ +class B: + def foo(self, a): + pass + + +class C1(B): + def foo(self, *a): + pass + + +class C2(B): + def foo(self): + pass + + +class C3(B): + def foo(self, **a): + pass + + +class C4(B): + def foo(self, a): + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/BothArgsKwargs.py b/python/testData/inspections/PyMethodOverridingInspection/BothArgsKwargs.py new file mode 100644 index 000000000000..82753b254177 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/BothArgsKwargs.py @@ -0,0 +1,23 @@ +class B1: + def foo(self, *args, **kwargs): + raise NotImplementedError() + + +class C1(B1): + def foo(self): # pass + pass + + +class C2(B1): + def foo(self, arg1): # pass + pass + + +class B3: + def foo(self, arg1, *args, **kwargs): + raise NotImplementedError() + + +class C3(B3): + def foo(self, arg1, arg2=None): # fail + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/DefaultArgument.py b/python/testData/inspections/PyMethodOverridingInspection/DefaultArgument.py new file mode 100644 index 000000000000..20a91efce66b --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/DefaultArgument.py @@ -0,0 +1,8 @@ +class B: + def foo(self, arg1): + pass + + +class C(B): + def foo(self, arg1, arg2=None): #pass + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/ExtraKwargs.py b/python/testData/inspections/PyMethodOverridingInspection/ExtraKwargs.py new file mode 100644 index 000000000000..407d101ae98e --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/ExtraKwargs.py @@ -0,0 +1,8 @@ +class B: + def foo(self, x=1): + pass + + +class C(B): + def foo(self, **kwargs): + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/InitNew.py b/python/testData/inspections/PyMethodOverridingInspection/InitNew.py new file mode 100644 index 000000000000..d97453e13b69 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/InitNew.py @@ -0,0 +1,20 @@ +class B: + def __init__(self): + pass + + def __new__(self): + pass + + def foo(self, a): + pass + + +class C(B): + def __init__(self, a): # different but ok because __init__ is special + pass + + def __new__(self, p, q): # different but ok because __new__ is special + pass + + def foo(self, s, t): + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/LessArgumentsPlusDefaults.py b/python/testData/inspections/PyMethodOverridingInspection/LessArgumentsPlusDefaults.py new file mode 100644 index 000000000000..4ef9eba27485 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/LessArgumentsPlusDefaults.py @@ -0,0 +1,8 @@ +class B: + def foo(self, arg1, arg2=None): + pass + + +class C(B): + def foo(self, arg1=None): #fail + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/LessParametersAndKwargs.py b/python/testData/inspections/PyMethodOverridingInspection/LessParametersAndKwargs.py new file mode 100644 index 000000000000..502b9f6ff6f3 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/LessParametersAndKwargs.py @@ -0,0 +1,8 @@ +class B: + def foo(self, arg1, arg2=None, arg3=None, arg4=None): + pass + + +class C(B): + def foo(self, arg1, arg2=None, arg3=None, **kwargs): #pass + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/NotOverridingMethod.py b/python/testData/inspections/PyMethodOverridingInspection/NotOverridingMethod.py new file mode 100644 index 000000000000..26e9f8324bd8 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/NotOverridingMethod.py @@ -0,0 +1,7 @@ +class B: + pass + + +class C(B): + def foo(self): + pass diff --git a/python/testData/inspections/PyMethodOverridingInspection/RequiredParameterAndKwargs.py b/python/testData/inspections/PyMethodOverridingInspection/RequiredParameterAndKwargs.py new file mode 100644 index 000000000000..f0cc84b532c1 --- /dev/null +++ b/python/testData/inspections/PyMethodOverridingInspection/RequiredParameterAndKwargs.py @@ -0,0 +1,9 @@ +class B: + def foo(self): + pass + + +class C(B): + def foo(self, p1, **kwargs): #fail + pass + diff --git a/python/testData/inspections/PyMethodOverridingInspection/test.py b/python/testData/inspections/PyMethodOverridingInspection/test.py deleted file mode 100644 index e03131da59ff..000000000000 --- a/python/testData/inspections/PyMethodOverridingInspection/test.py +++ /dev/null @@ -1,144 +0,0 @@ -class c1: - def foo(self, a): - pass - -class c2(c1): - def foo(self, *a): - pass - -class c3(c1): - def foo(self): - pass - -class c4(c1): - def foo(self, **a): - pass - -class c5(c1): - def foo(self, a): - pass - -class c6: - pass - -class c7(c6): - def foo(self): - pass - -class c8: - def __init__(self): - pass - - def __new__(self): - pass - - def foo(self, a): - pass - - -class c9(c8): - def __init__(self, a): # different but ok because __init__ is special - pass - - def __new__(self, p, q): # different but ok because __new__ is special - pass - - def foo(self, s, t): - pass - -class c10: - def foo(self, a, b): - pass - -class c11(c10): - def foo(self, *b): - pass - -class c12(c4): - def foo(self): - pass - -class c13: - def foo(self, *args): - pass - -class c14(c13): - def foo(self): - pass - -class c15: # PY-1083 - def foo(self, x = 1): - pass - -class c16: - def foo(self, **kwargs): - pass - - -# PY-6700 -class c17: - def foo(self, **kwargs): - pass - -class c18(c17): - def foo(self, arg1=None, **kwargs): # pass - pass - -class c19: - def foo(self, *args, **kwargs): - raise NotImplementedError() - -class c20(c19): - def foo(self): # pass - pass - -class c21(c19): - def foo(self, arg1): # pass - pass - -class c22: - def foo(self, arg1, *args, **kwargs): - raise NotImplementedError() - -class c23(c22): - def foo(self, arg1, arg2=None): # fail - pass - - -# PY-7157 -class c24: - def foo(self, arg1): - pass - -class c25(c24): - def foo(self, arg1, arg2=None): #pass - pass - - -# PY-7162 -class c26: - def foo(self, arg1, arg2=None): - pass - -class c27(c26): - def foo(self, arg1=None): #fail - pass - - -# PY-7159 -class c28: - def foo(self): - pass - -class c29(c28): - def foo(self, p1, **kwargs): #fail - pass - - -class c30: - def foo(self, arg1, arg2=None, arg3=None, arg4=None): - pass - -class c31(c30): - def foo(self, arg1, arg2=None, arg3=None, **kwargs): #pass - pass diff --git a/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java b/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java new file mode 100644 index 000000000000..ca385138cc4d --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyMethodOverridingInspectionTest.java @@ -0,0 +1,67 @@ +package com.jetbrains.python; + +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.inspections.PyMethodOverridingInspection; + +/** + * @author vlan + */ +public class PyMethodOverridingInspectionTest extends PyTestCase { + private static final String TEST_DIRECTORY = "inspections/PyMethodOverridingInspection/"; + + public void testArgsKwargsOverrideArg() { + doTest(); + } + + public void testNotOverridingMethod() { + doTest(); + } + + public void testInitNew() { + doTest(); + } + + public void testArgsKwargsAsAllowAnything() { + doTest(); + } + + // PY-1083 + public void testExtraKwargs() { + doTest(); + } + + // PY-6700 + public void testBothArgsKwargs() { + doTest(); + } + + // PY-6700 + public void testArgAndKwargs() { + doTest(); + } + + // PY-7157 + public void testDefaultArgument() { + doTest(); + } + + // PY-7162 + public void testLessArgumentsPlusDefaults() { + doTest(); + } + + public void testLessParametersAndKwargs() { + doTest(); + } + + // PY-7159 + public void testRequiredParameterAndKwargs() { + doTest(); + } + + private void doTest() { + myFixture.configureByFile(TEST_DIRECTORY + getTestName(false) + ".py"); + myFixture.enableInspections(PyMethodOverridingInspection.class); + myFixture.checkHighlighting(true, false, false); + } +} diff --git a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java index 7439dddfc5fa..4e0637f39e5e 100644 --- a/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java +++ b/python/testSrc/com/jetbrains/python/PythonAllTestsSuite.java @@ -95,6 +95,7 @@ public class PythonAllTestsSuite { PyUnresolvedReferencesInspectionTest.class, PyCallingNonCallableInspectionTest.class, PyUnboundLocalVariableInspectionTest.class, + PyMethodOverridingInspectionTest.class, PyUnusedImportTest.class, PyDeprecationTest.class, PythonHighlightingLexerTest.class, diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java index 0ed83ce7872c..8af7f625bd8d 100644 --- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java +++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java @@ -62,11 +62,6 @@ public class PythonInspectionsTest extends PyTestCase { doTest(getTestName(false), inspection); } - public void testPyMethodOverridingInspection() { - LocalInspectionTool inspection = new PyMethodOverridingInspection(); - doHighlightingTest(inspection); - } - public void testPyTrailingSemicolonInspection() { LocalInspectionTool inspection = new PyTrailingSemicolonInspection(); doTest(getTestName(false), inspection); From ba19e0d6665332d41292aeb9440590d049eb8733 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Mon, 18 Feb 2013 19:29:52 +0400 Subject: [PATCH 08/11] Fixed overriding method signature inspection for property setters (PY-7725) --- .../src/com/jetbrains/python/psi/PyUtil.java | 17 +++++++++++++++++ .../PyOverridingMethodsSearchExecutor.java | 12 ++++++++++-- .../search/PySuperMethodsSearchExecutor.java | 16 +++++++++++++--- .../PropertySetter.py | 18 ++++++++++++++++++ .../PyMethodOverridingInspectionTest.java | 5 +++++ 5 files changed, 63 insertions(+), 5 deletions(-) create mode 100644 python/testData/inspections/PyMethodOverridingInspection/PropertySetter.py diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 12a2870fe860..3e923c66fbd2 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -624,6 +624,23 @@ public class PyUtil { return false; } + @NotNull + public static AccessDirection getPropertyAccessDirection(@NotNull PyFunction function) { + final Property property = function.getProperty(); + if (property != null) { + if (property.getGetter().valueOrNull() == function) { + return AccessDirection.READ; + } + if (property.getSetter().valueOrNull() == function) { + return AccessDirection.WRITE; + } + else if (property.getDeleter().valueOrNull() == function) { + return AccessDirection.DELETE; + } + } + return AccessDirection.READ; + } + public static class KnownDecoratorProviderHolder { public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME); diff --git a/python/src/com/jetbrains/python/psi/search/PyOverridingMethodsSearchExecutor.java b/python/src/com/jetbrains/python/psi/search/PyOverridingMethodsSearchExecutor.java index dff229662d3b..aaa0d3a24319 100644 --- a/python/src/com/jetbrains/python/psi/search/PyOverridingMethodsSearchExecutor.java +++ b/python/src/com/jetbrains/python/psi/search/PyOverridingMethodsSearchExecutor.java @@ -4,8 +4,7 @@ import com.intellij.openapi.application.AccessToken; import com.intellij.openapi.application.ApplicationManager; import com.intellij.util.Processor; import com.intellij.util.QueryExecutor; -import com.jetbrains.python.psi.PyClass; -import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.*; import org.jetbrains.annotations.NotNull; /** @@ -21,6 +20,15 @@ public class PyOverridingMethodsSearchExecutor implements QueryExecutor Date: Mon, 18 Feb 2013 17:31:10 +0100 Subject: [PATCH 09/11] Fixed thread leak in multiprocess debugger. --- .../debugger/pydev/MultiProcessDebugger.java | 26 +++++++++----- .../debugger/pydev/ProcessDebugger.java | 18 ++++++---- .../python/debugger/pydev/RemoteDebugger.java | 34 +++++++++---------- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java index 2735d912ac82..52cbabc8c7fe 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java @@ -81,6 +81,8 @@ public class MultiProcessDebugger implements ProcessDebugger { myMainDebugger.waitForConnect(); + disposeAcceptor(); + myDebugProcessAcceptor = new DebuggerProcessAcceptor(this, myServerSocket); ApplicationManager.getApplication().executeOnPooledThread(myDebugProcessAcceptor); } @@ -109,6 +111,14 @@ public class MultiProcessDebugger implements ProcessDebugger { return serverSocket; } + @Override + public void close() { + myMainDebugger.close(); + for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { + d.close(); + } + disposeAcceptor(); + } @Override public void disconnect() { @@ -116,8 +126,13 @@ public class MultiProcessDebugger implements ProcessDebugger { for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { d.disconnect(); } + disposeAcceptor(); + } + + private void disposeAcceptor() { if (myDebugProcessAcceptor != null) { myDebugProcessAcceptor.disconnect(); + myDebugProcessAcceptor = null; } } @@ -285,14 +300,6 @@ public class MultiProcessDebugger implements ProcessDebugger { debugger(threadId).suspendThread(threadId); } - @Override - public void close() { - myMainDebugger.close(); - for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { - d.close(); - } - } - @Override public void run() throws PyDebuggerException { myMainDebugger.run(); @@ -428,8 +435,9 @@ public class MultiProcessDebugger implements ProcessDebugger { catch (IOException ignore) { } myServerSocket = null; - myShouldAccept = false; } + myShouldAccept = false; + myMultiProcessDebugger = null; } } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java index b2bb6408fa7e..fd6d27f0ba63 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/ProcessDebugger.java @@ -15,12 +15,6 @@ import java.util.List; * @author traff */ public interface ProcessDebugger { - boolean isConnected(); - - void waitForConnect() throws Exception; - - void disconnect(); - String handshake() throws PyDebuggerException; PyDebugValue evaluate(String threadId, @@ -55,8 +49,20 @@ public interface ProcessDebugger { void suspendThread(String threadId); + /** + * Disconnects current debug process. Closes all resources. + */ void close(); + boolean isConnected(); + + void waitForConnect() throws Exception; + + /** + * Disconnects currently connected process. After that it can wait for the next. + */ + void disconnect(); + void run() throws PyDebuggerException; void smartStepInto(String threadId, String functionName); diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java index 711c08216d9f..557a44995abd 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java @@ -96,23 +96,6 @@ public class RemoteDebugger implements ProcessDebugger { } } - @Override - public void disconnect() { - synchronized (mySocketObject) { - myConnected = false; - - if (mySocket != null && !mySocket.isClosed()) { - try { - mySocket.close(); - } - catch (IOException ignore) { - } - } - } - - cleanUp(); - } - @Override public String handshake() throws PyDebuggerException { final VersionCommand command = new VersionCommand(this, LOCAL_VERSION, SystemInfo.isUnix ? "UNIX" : "WIN"); @@ -366,6 +349,23 @@ public class RemoteDebugger implements ProcessDebugger { fireCloseEvent(); } + @Override + public void disconnect() { + synchronized (mySocketObject) { + myConnected = false; + + if (mySocket != null && !mySocket.isClosed()) { + try { + mySocket.close(); + } + catch (IOException ignore) { + } + } + } + + cleanUp(); + } + @Override public void run() throws PyDebuggerException { new RunCommand(this).execute(); From ca546f2329da72ddedda42ad21cb5755161f414e Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Mon, 18 Feb 2013 17:32:22 +0100 Subject: [PATCH 10/11] Fixed memory leak in debug console. --- .../python/console/PythonDebugLanguageConsoleView.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java index ebdd95d2facb..7eeb71599889 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonDebugLanguageConsoleView.java @@ -20,6 +20,7 @@ import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.util.Disposer; import icons.PythonIcons; import org.jetbrains.annotations.NotNull; @@ -53,6 +54,9 @@ public class PythonDebugLanguageConsoleView extends JPanel implements ConsoleVie add(myPydevConsoleView.getComponent(), PYDEV_CONSOLE_PANEL); showDebugConsole(PyConsoleOptionsProvider.getInstance(project).isShowDebugConsoleByDefault()); + + Disposer.register(this, myPydevConsoleView); + Disposer.register(this, myTextConsole); } public PythonDebugLanguageConsoleView(final Project project, Sdk sdk) { @@ -249,4 +253,6 @@ public class PythonDebugLanguageConsoleView extends JPanel implements ConsoleVie } } } + + } From 450750f3d8c3a565b0e82677bffc0a24915aad9d Mon Sep 17 00:00:00 2001 From: Dmitry Trofimov Date: Mon, 18 Feb 2013 20:00:16 +0100 Subject: [PATCH 11/11] Fixed CME (PY-8686). --- .../debugger/pydev/MultiProcessDebugger.java | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java index 52cbabc8c7fe..83651718cf82 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/MultiProcessDebugger.java @@ -113,17 +113,23 @@ public class MultiProcessDebugger implements ProcessDebugger { @Override public void close() { - myMainDebugger.close(); - for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (ProcessDebugger d : allDebuggers()) { d.close(); } disposeAcceptor(); } + private List allDebuggers() { + List result = Lists.newArrayList(myMainDebugger); + synchronized (myOtherDebuggers) { + result.addAll(myOtherDebuggers); + } + return result; + } + @Override public void disconnect() { - myMainDebugger.disconnect(); - for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (ProcessDebugger d : allDebuggers()) { d.disconnect(); } disposeAcceptor(); @@ -245,7 +251,7 @@ public class MultiProcessDebugger implements ProcessDebugger { private void cleanOtherDebuggers() { synchronized (myOtherDebuggers) { - removeDisconnected(Lists.newArrayList(myOtherDebuggers)); + removeDisconnected(getOtherDebuggers()); } } @@ -264,13 +270,15 @@ public class MultiProcessDebugger implements ProcessDebugger { } } - myOtherDebuggers.clear(); - myOtherDebuggers.addAll(newList); + synchronized (myOtherDebuggers) { + myOtherDebuggers.clear(); + myOtherDebuggers.addAll(newList); + } } } private void collectAndRegisterOtherDebuggersThreads(List threads) { - for (RemoteDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (RemoteDebugger d : getOtherDebuggers()) { threads.addAll(d.getThreads()); for (PyThreadInfo t : d.getThreads()) { myThreadRegistry.register(t.getId(), d); @@ -278,19 +286,23 @@ public class MultiProcessDebugger implements ProcessDebugger { } } + private ArrayList getOtherDebuggers() { + synchronized (myOtherDebuggers) { + return Lists.newArrayList(myOtherDebuggers); + } + } + @Override public void execute(@NotNull AbstractCommand command) { - myMainDebugger.execute(command); - for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (ProcessDebugger d : allDebuggers()) { d.execute(command); } } @Override public void suspendAllThreads() { - myMainDebugger.suspendAllThreads(); - for (ProcessDebugger d : myOtherDebuggers) { + for (ProcessDebugger d : allDebuggers()) { d.suspendAllThreads(); } } @@ -317,32 +329,28 @@ public class MultiProcessDebugger implements ProcessDebugger { @Override public void setTempBreakpoint(String type, String file, int line) { - myMainDebugger.setTempBreakpoint(type, file, line); - for (ProcessDebugger d : myOtherDebuggers) { + for (ProcessDebugger d : allDebuggers()) { d.setTempBreakpoint(type, file, line); } } @Override public void removeTempBreakpoint(String file, int line) { - myMainDebugger.removeTempBreakpoint(file, line); - for (ProcessDebugger d : myOtherDebuggers) { + for (ProcessDebugger d : allDebuggers()) { d.removeTempBreakpoint(file, line); } } @Override public void setBreakpoint(String typeId, String file, int line, String condition, String logExpression) { - myMainDebugger.setBreakpoint(typeId, file, line, condition, logExpression); - for (ProcessDebugger d : myOtherDebuggers) { + for (ProcessDebugger d : allDebuggers()) { d.setBreakpoint(typeId, file, line, condition, logExpression); } } @Override public void removeBreakpoint(String typeId, String file, int line) { - myMainDebugger.removeBreakpoint(typeId, file, line); - for (ProcessDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (ProcessDebugger d : allDebuggers()) { d.removeBreakpoint(typeId, file, line); } } @@ -458,18 +466,14 @@ public class MultiProcessDebugger implements ProcessDebugger { @Override public void addExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { - myMainDebugger.execute(factory.createAddCommand(myMainDebugger)); - - for (RemoteDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (RemoteDebugger d : allDebuggers()) { d.execute(factory.createAddCommand(d)); } } @Override public void removeExceptionBreakpoint(ExceptionBreakpointCommandFactory factory) { - myMainDebugger.execute(factory.createRemoveCommand(myMainDebugger)); - - for (RemoteDebugger d : Lists.newArrayList(myOtherDebuggers)) { + for (RemoteDebugger d : allDebuggers()) { d.execute(factory.createRemoveCommand(d)); } }