From 47fc79d09ef2793cd5fcc468d1fe7454bc5e4265 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Tue, 20 May 2014 02:44:55 +0400 Subject: [PATCH] PY-12810 "imported" urls are not supported by {% url %} tag ( "patterns +=" now supported) --- .../platform/NewDirectoryProjectDialog.java | 2 + .../com/jetbrains/python/psi/PsiQuery.java | 2 + .../src/com/jetbrains/python/psi/PyUtil.java | 23 +++ .../psi/impl/PyAnyExpressionEvaluator.java | 105 +++++++++++++ .../python/psi/impl/PyBlockEvaluator.java | 5 +- .../python/psi/impl/PyEvaluator.java | 51 +++--- .../psi/impl/PyExpressionEvaluator.java | 147 ------------------ .../python/PyExpressionEvaluatorTest.java | 133 ---------------- 8 files changed, 164 insertions(+), 304 deletions(-) create mode 100644 python/src/com/jetbrains/python/psi/impl/PyAnyExpressionEvaluator.java delete mode 100644 python/src/com/jetbrains/python/psi/impl/PyExpressionEvaluator.java delete mode 100644 python/testSrc/com/jetbrains/python/PyExpressionEvaluatorTest.java diff --git a/platform/platform-impl/src/com/intellij/platform/NewDirectoryProjectDialog.java b/platform/platform-impl/src/com/intellij/platform/NewDirectoryProjectDialog.java index 757782851e80..c902aaff667b 100644 --- a/platform/platform-impl/src/com/intellij/platform/NewDirectoryProjectDialog.java +++ b/platform/platform-impl/src/com/intellij/platform/NewDirectoryProjectDialog.java @@ -61,6 +61,8 @@ public class NewDirectoryProjectDialog extends DialogWrapper { setTitle("Create New Project"); init(); + myLocationField = new TextFieldWithBrowseButton(); + myLocationLabel = new JLabel(); myLocationLabel.setLabelFor(myLocationField.getChildComponent()); new LocationNameFieldsBinding(project, myLocationField, myProjectNameTextField, ProjectUtil.getBaseDir(), "Select Location for Project Directory"); diff --git a/python/src/com/jetbrains/python/psi/PsiQuery.java b/python/src/com/jetbrains/python/psi/PsiQuery.java index d53469d2dc66..92be4c725bfc 100644 --- a/python/src/com/jetbrains/python/psi/PsiQuery.java +++ b/python/src/com/jetbrains/python/psi/PsiQuery.java @@ -62,7 +62,9 @@ public class PsiQuery { /** + * TODO: Support types? * Filter children by function call + * @return {@link com.jetbrains.python.psi.PsiQuery} backed by {@link com.jetbrains.python.psi.PyCallExpression} */ @NotNull public PsiQuery childrenCall(@NotNull final FQNamesProvider name) { diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 255e88e9f07a..1700a49004ec 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -850,6 +850,29 @@ public class PyUtil { return null; } + + /** + * Converts collection to list of certain type + * @param expression expression of collection type + * @param elementClass expected element type + * @param expected element type + * @return list of elements of expected element type + */ + @NotNull + public static List asList(@Nullable final Collection expression, @NotNull final Class elementClass) { + if ((expression == null) || expression.isEmpty()) { + return Collections.emptyList(); + } + final List result = new ArrayList(); + for (final Object element : expression) { + final T toAdd = as(element, elementClass); + if (toAdd != null) { + result.add(toAdd); + } + } + return result; + } + public static class KnownDecoratorProviderHolder { public static PyKnownDecoratorProvider[] KNOWN_DECORATOR_PROVIDERS = Extensions.getExtensions(PyKnownDecoratorProvider.EP_NAME); diff --git a/python/src/com/jetbrains/python/psi/impl/PyAnyExpressionEvaluator.java b/python/src/com/jetbrains/python/psi/impl/PyAnyExpressionEvaluator.java new file mode 100644 index 000000000000..b006ac1c2f17 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyAnyExpressionEvaluator.java @@ -0,0 +1,105 @@ +package com.jetbrains.python.psi.impl; + +import com.jetbrains.python.psi.*; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * Evaluator tht supports expression of any type (it evaluates any expression, concatenating several expressions to list etc) + */ +public class PyAnyExpressionEvaluator extends PyEvaluator { + private final boolean myEvalSequence; + + /** + * @param evalSequence evaluate sequencies ((a,b) + (c,d) == (a,b,c,d)) or store them as single ((a,b) + (c,d) = [(a,b), (c,d)]) + */ + public PyAnyExpressionEvaluator(final boolean evalSequence) { + myEvalSequence = evalSequence; + } + + @NotNull + @Override + public Object evaluate(final PyExpression expr) { + final Object evaluate = super.evaluate(expr); + return ((evaluate != null) ? evaluate : expr); + } + + @Override + public Object concatenate(final Object lhs, final Object rhs) { + final Object evaluate = super.concatenate(lhs, rhs); + return ((evaluate != null) ? evaluate : Arrays.asList(lhs, rhs)); + } + + @Override + protected Object evaluateReferenceExpression(final PyReferenceExpression expr) { + final Object evaluate = super.evaluateReferenceExpression(expr); + return ((evaluate != null) ? evaluate : expr); + } + + @Override + protected Object evaluateCall(final PyCallExpression call) { + final Object evaluate = super.evaluateCall(call); + return ((evaluate != null) ? evaluate : call); + } + + @Override + protected Object evaluateSequenceExpression(final PySequenceExpression expr) { + return myEvalSequence ? super.evaluateSequenceExpression(expr) : expr; + } + + /** + * Evaluates expression to single element + * @param expression exp to eval + * @param aClass expected class + * @param expected class + * @return instance of aClass, or null if failed to eval + */ + @Nullable + public static T evaluateOne(@NotNull final PyExpression expression, @NotNull final Class aClass) { + final PyAnyExpressionEvaluator evaluator = new PyAnyExpressionEvaluator(false); + final Object evaluate = evaluator.evaluate(expression); + final T resultSingle = PyUtil.as(evaluate, aClass); + if (resultSingle != null) { + return resultSingle; + } + final List resultMultiple = PyUtil.as(evaluate, List.class); + if ((resultMultiple != null) && !resultMultiple.isEmpty()) { + return PyUtil.as(resultMultiple.get(0), aClass); + } + return null; + } + + + /** + * Evaluates expression to string + * @param expression exp to eval + * @return string, or null if failed to eval + */ + @Nullable + public static String evaluateString(@NotNull final PyExpression expression) { + return PyUtil.as(new PyAnyExpressionEvaluator(false).evaluate(expression), String.class); + } + + /** + * Evaluates expression as list of values + * @param expression exp to eval + * @param aClass expected element class + * @param expected element class + * @return a list of elements of expected type + */ + @NotNull + public static List evaluateIterable(@NotNull final PyExpression expression, @NotNull final Class aClass) { + final PyAnyExpressionEvaluator evaluator = new PyAnyExpressionEvaluator(true); + final Object evaluate = evaluator.evaluate(expression); + final T resultSingle = PyUtil.as(evaluate, aClass); + if (resultSingle != null) { + return Collections.singletonList(resultSingle); + } + return PyUtil.asList(PyUtil.as(evaluate, List.class), aClass); + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java b/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java index 016bfbedab68..f23ed3ac06cf 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java +++ b/python/src/com/jetbrains/python/psi/impl/PyBlockEvaluator.java @@ -26,7 +26,6 @@ import java.util.*; /** * - * @see {@link com.jetbrains.python.psi.impl.PyExpressionEvaluator} (TODO: merge both classes?) * @author yole */ public class PyBlockEvaluator { @@ -99,7 +98,7 @@ public class PyBlockEvaluator { Object currentValue = myNamespace.get(name); if (currentValue != null) { Object rhs = prepareEvaluator().evaluate(node.getValue()); - myNamespace.put(name, PyEvaluator.concatenate(currentValue, rhs)); + myNamespace.put(name, prepareEvaluator().concatenate(currentValue, rhs)); } if (myDeclarationsToTrack.contains(name)) { List declarations = myDeclarations.get(name); @@ -189,7 +188,7 @@ public class PyBlockEvaluator { Object value = myNamespace.get(nameBeingExtended); if (value instanceof List) { Object argValue = prepareEvaluator().evaluate(arg); - myNamespace.put(nameBeingExtended, PyEvaluator.concatenate(value, argValue)); + myNamespace.put(nameBeingExtended, prepareEvaluator().concatenate(value, argValue)); } if (myDeclarationsToTrack.contains(nameBeingExtended)) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyEvaluator.java b/python/src/com/jetbrains/python/psi/impl/PyEvaluator.java index a006c2728121..d4300be4eecd 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyEvaluator.java +++ b/python/src/com/jetbrains/python/psi/impl/PyEvaluator.java @@ -24,7 +24,7 @@ import com.jetbrains.python.psi.resolve.PyResolveContext; import java.util.*; /** - * TODO: Merge {@link com.jetbrains.python.psi.impl.PyExpressionEvaluator}, PythonDataflowUtil, {@link com.jetbrains.python.psi.impl.PyConstantExpressionEvaluator} and {@link com.jetbrains.python.psi.impl.PyEvaluator} and all its inheritors and improve Abstract Interpretation + * TODO: Merge PythonDataflowUtil, {@link com.jetbrains.python.psi.impl.PyConstantExpressionEvaluator} and {@link com.jetbrains.python.psi.impl.PyEvaluator} and all its inheritors and improve Abstract Interpretation * @author yole */ public class PyEvaluator { @@ -49,25 +49,7 @@ public class PyEvaluator { return evaluate(((PyParenthesizedExpression)expr).getContainedExpression()); } if (expr instanceof PySequenceExpression) { - PyExpression[] elements = ((PySequenceExpression)expr).getElements(); - if (expr instanceof PyDictLiteralExpression) { - Map result = new HashMap(); - for (PyKeyValueExpression keyValueExpression : ((PyDictLiteralExpression)expr).getElements()) { - Object dictKey = evaluate(keyValueExpression.getKey()); - if (dictKey != null) { - PyExpression value = keyValueExpression.getValue(); - result.put(dictKey, myEvaluateCollectionItems ? evaluate(value) : value); - } - } - return result; - } - else { - List result = new ArrayList(); - for (PyExpression element : elements) { - result.add(myEvaluateCollectionItems ? evaluate(element) : element); - } - return result; - } + return evaluateSequenceExpression((PySequenceExpression)expr); } if (expr instanceof PyCallExpression) { return evaluateCall((PyCallExpression)expr); @@ -92,7 +74,34 @@ public class PyEvaluator { return null; } - public static Object concatenate(Object lhs, Object rhs) { + /** + * Evaluates some sequence (tuple, list) + * @param expr seq expression + * @return evaluated seq + */ + protected Object evaluateSequenceExpression(PySequenceExpression expr) { + PyExpression[] elements = expr.getElements(); + if (expr instanceof PyDictLiteralExpression) { + Map result = new HashMap(); + for (PyKeyValueExpression keyValueExpression : ((PyDictLiteralExpression)expr).getElements()) { + Object dictKey = evaluate(keyValueExpression.getKey()); + if (dictKey != null) { + PyExpression value = keyValueExpression.getValue(); + result.put(dictKey, myEvaluateCollectionItems ? evaluate(value) : value); + } + } + return result; + } + else { + List result = new ArrayList(); + for (PyExpression element : elements) { + result.add(myEvaluateCollectionItems ? evaluate(element) : element); + } + return result; + } + } + + public Object concatenate(Object lhs, Object rhs) { if (lhs instanceof String && rhs instanceof String) { return (String)lhs + (String)rhs; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyExpressionEvaluator.java b/python/src/com/jetbrains/python/psi/impl/PyExpressionEvaluator.java deleted file mode 100644 index 72db93dc0b87..000000000000 --- a/python/src/com/jetbrains/python/psi/impl/PyExpressionEvaluator.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.jetbrains.python.psi.impl; - -import com.intellij.psi.util.PsiTreeUtil; -import com.jetbrains.python.PyTokenTypes; -import com.jetbrains.python.psi.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.List; - -/** - * Engine to evaluate expressions. - *

- * Supports references and concatinations (sometimes!) - * - * @author Ilya.Kazakevich - * @see {@link com.jetbrains.python.psi.impl.PyBlockEvaluator} (TODO: merge both classes?) - */ -public final class PyExpressionEvaluator { - private static final int MAX_STEP = 100; - - private PyExpressionEvaluator() { - } - - - /** - * Evaluate expression to string - * - * @param expression expression to evaluate - * @return string (or empty if cant evaluate) - */ - @NotNull - public static String evaluateString(@NotNull final PyExpression expression) { - final List expressions = evaluateRaw(expression, PyStringLiteralExpression.class); - final StringBuilder builder = new StringBuilder(); - for (final PyStringLiteralExpression literalExpression : expressions) { - builder.append(literalExpression.getStringValue()); - } - - return builder.toString(); - } - - /** - * Evaluate expression to long - * - * @param expression expression to evaluate - * @return long (or 0 if cant evaluate) - */ - public static long evaluateLong(@NotNull final PyExpression expression) { - final List expressions = evaluateRaw(expression, PyNumericLiteralExpression.class); - long result = 0L; - for (final PyNumericLiteralExpression numericLiteralExpression : expressions) { - final Long value = numericLiteralExpression.getLongValue(); - result += value != null ? value : 0L; - } - return result; - } - - - /** - * Evaluate expression to pack of elements (some iterables could be evaluated to it) - * - * @param expression expression to evaluate - * @param aClass expected class of elements - * @return pack of elements (or empty if cant evaluate) - */ - @NotNull - public static List evaluateIterable(@NotNull final PyExpression expression, @NotNull final Class aClass) { - final List list = new ArrayList(); - for (final PyElement possibleIterable : evaluateRaw(expression, PyElement.class)) { - if ((possibleIterable instanceof PyParenthesizedExpression) || (possibleIterable instanceof PyListLiteralExpression)) { - list.addAll(PsiTreeUtil.findChildrenOfType(possibleIterable, aClass)); - } - } - return list; - } - - /** - * Evaluates expression to some element - * @param expression expression to evaluate - * @param aClass expected element type - * @param expected element type - * @return element or null if can't evaluate - */ - @Nullable - public static T evaluateOne(@NotNull final PyExpression expression, @NotNull final Class aClass) { - final List list = evaluateRaw(expression, aClass); - if (list.isEmpty()) { - return null; - } - return list.get(0); - } - - - /** - * Evaluates expression to one or more elements (all elements should be concatinated). - * I.e: i = 1 + 2. Evaluating "i" would lead to "1" and "2" separately (and not summed like in {@link #evaluateLong(com.jetbrains.python.psi.PyExpression)} - * @param expression expression to evaluate - * @param aClass expected elements type - * @param expected elements type - * @return a pack of elements to concatinate or empty if can't evaluate - */ - @NotNull - public static List evaluateRaw(@NotNull final PyExpression expression, @NotNull final Class aClass) { - int step = 0; - PyElement currentElement = expression; - final List result = new ArrayList(); - while (step < MAX_STEP) { - PyElement newElement = null; - - // TODO: Use visitor? - if (currentElement instanceof PyReferenceExpression) { - newElement = PyUtil.as(((PyReferenceExpression)currentElement).getReference().resolve(), PyElement.class); - } - else if (currentElement instanceof PyTargetExpression) { - newElement = ((PyTargetExpression)currentElement).findAssignedValue(); - } - else if (currentElement instanceof PyBinaryExpression) { - final PyBinaryExpression newExpression = (PyBinaryExpression)currentElement; - final PyElementType operator = newExpression.getOperator(); - if (PyTokenTypes.PLUS.equals(operator)) { - final PyExpression leftExpression = newExpression.getLeftExpression(); - final PyExpression rightExpression = newExpression.getRightExpression(); - if (leftExpression != null) { - result.addAll(evaluateRaw(leftExpression, aClass)); - } - if (rightExpression != null) { - result.addAll(evaluateRaw(rightExpression, aClass)); - } - } - } - - if (newElement == null) { - break; - } - - currentElement = newElement; - step++; - } - final T elementToAdd = PyUtil.as(currentElement, aClass); - if (elementToAdd != null) { - result.add(elementToAdd); - } - return result; - } -} diff --git a/python/testSrc/com/jetbrains/python/PyExpressionEvaluatorTest.java b/python/testSrc/com/jetbrains/python/PyExpressionEvaluatorTest.java deleted file mode 100644 index f36c5e4c4b9c..000000000000 --- a/python/testSrc/com/jetbrains/python/PyExpressionEvaluatorTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.jetbrains.python; - -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.psi.PsiFile; -import com.jetbrains.python.fixtures.PyTestCase; -import com.jetbrains.python.psi.*; -import com.jetbrains.python.psi.impl.PyExpressionEvaluator; -import org.hamcrest.Matchers; -import org.jetbrains.annotations.NotNull; -import org.junit.Assert; - -import java.util.List; - -/** - * Tests {@link com.jetbrains.python.psi.impl.PyExpressionEvaluator} - * - * @author Ilya.Kazakevich - */ -public class PyExpressionEvaluatorTest extends PyTestCase { - - public void testLiteralReferences() { - final PyTargetExpression myValue = createFileAndExpression("my_value", - "x = 2", - "y = x", - "my_value = y" - ); - final List result = PyExpressionEvaluator.evaluateRaw(myValue, PyNumericLiteralExpression.class); - Assert.assertThat("Expected list of 1 long element", result.size(), Matchers.is(1)); - Assert.assertNotNull("Failed to evaluate chain of literals (got null)", result); - final PyNumericLiteralExpression resultElement = result.get(0); - Assert.assertThat("Evaluated incorrectly", resultElement.getLongValue(), Matchers.equalTo(2L)); - } - - - public void testCall() { - final PyTargetExpression myValue = createFileAndExpression("my_value", - "x = abs(42)", - "y = x", - "my_value = y" - ); - final List result = PyExpressionEvaluator.evaluateRaw(myValue, PyCallExpression.class); - Assert.assertNotNull("Failed to evaluate chain of functions (got null)", result); - Assert.assertThat("Expected list of 1 call element", result.size(), Matchers.is(1)); - final PyExpression callee = result.get(0).getCallee(); - Assert.assertNotNull("Found empty callee", callee); - Assert.assertThat("Evaluated incorrectly", callee.getName(), Matchers.equalTo("abs")); - } - - public void testStringConcat() { - final PyTargetExpression myValue = createFileAndExpression("my_value", - "x = 'go'", - "y = x + ' away'", - "my_value = y" - ); - - final List expressions = PyExpressionEvaluator.evaluateRaw(myValue, PyStringLiteralExpression.class); - Assert.assertThat("Expected list of 2 string elements", expressions.size(), Matchers.is(2)); - Assert.assertThat("Evaluated incorrectly", expressions.get(0).getStringValue(), Matchers.equalTo("go")); - Assert.assertThat("Evaluated incorrectly", expressions.get(1).getStringValue(), Matchers.equalTo(" away")); - } - - public void testNumberConcat() { - final PyTargetExpression myValue = createFileAndExpression("my_value", - "x = 2", - "y = x + 1", - "my_value = y" - ); - - final List expressions = PyExpressionEvaluator.evaluateRaw(myValue, PyNumericLiteralExpression.class); - Assert.assertThat("Expected list of 2 num elements", expressions.size(), Matchers.is(2)); - Assert.assertThat("Evaluated incorrectly", expressions.get(0).getLongValue(), Matchers.equalTo(2L)); - Assert.assertThat("Evaluated incorrectly", expressions.get(1).getLongValue(), Matchers.equalTo(1L)); - } - - public void testNumber() { - final long result = PyExpressionEvaluator.evaluateLong(createFileAndExpression("my_value", "x = 2", " y = 3", "my_value = x + y + 1")); - Assert.assertThat("Error summarizing numbers", result, Matchers.is(6L)); - } - - public void testString() { - final String result = PyExpressionEvaluator.evaluateString(createFileAndExpression("my_value", - "x = 'hello'", - " y = \" there\"", - "my_value = x + y + '!'")); - Assert.assertThat("Error in string concat", result, Matchers.is("hello there!")); - } - - public void testIterableTuples() { - final List value = PyExpressionEvaluator.evaluateIterable(createFileAndExpression("my_value", - "x = ('hello', 'from')", - " y = ('here', '!')", - "my_value = x + y"), - PyStringLiteralExpression.class - ); - Assert.assertThat("Expected list of elements", value.size(), Matchers.is(4)); - Assert.assertThat("Evaluated incorrectly", value.get(0).getStringValue(), Matchers.equalTo("hello")); - Assert.assertThat("Evaluated incorrectly", value.get(1).getStringValue(), Matchers.equalTo("from")); - Assert.assertThat("Evaluated incorrectly", value.get(2).getStringValue(), Matchers.equalTo("here")); - Assert.assertThat("Evaluated incorrectly", value.get(3).getStringValue(), Matchers.equalTo("!")); - } - - public void testIterableLists() { - final List value = PyExpressionEvaluator.evaluateIterable(createFileAndExpression("my_value", - "x = ['hello', 'from']", - " y = ['here'] + ['!']", - "my_value = x + y"), - PyStringLiteralExpression.class - ); - Assert.assertThat("Expected list of elements", value.size(), Matchers.is(4)); - Assert.assertThat("Evaluated incorrectly", value.get(0).getStringValue(), Matchers.equalTo("hello")); - Assert.assertThat("Evaluated incorrectly", value.get(1).getStringValue(), Matchers.equalTo("from")); - Assert.assertThat("Evaluated incorrectly", value.get(2).getStringValue(), Matchers.equalTo("here")); - Assert.assertThat("Evaluated incorrectly", value.get(3).getStringValue(), Matchers.equalTo("!")); - } - - /** - * Create file, fill it with data and return some expression from it - * - * @param varName name of expression to return - * @param lines lines of code - * @return expression - */ - @NotNull - private PyTargetExpression createFileAndExpression(@NotNull final String varName, @NotNull final String... lines) { - final PsiFile file = - PyElementGenerator.getInstance(myFixture.getProject()).createDummyFile(LanguageLevel.PYTHON27, StringUtil.join(lines, "\n")); - assert file instanceof PyFile : "PyElementGenerator created not py file?"; - final PyFile pyFile = (PyFile)file; - final PyTargetExpression result = pyFile.findTopLevelAttribute(varName); - assert result != null : "No var with name " + varName + " found"; - return result; - } -}