diff --git a/python/src/com/jetbrains/python/psi/PyDecorator.java b/python/src/com/jetbrains/python/psi/PyDecorator.java
index fba8d9b2e758..4fac6cdb1af1 100644
--- a/python/src/com/jetbrains/python/psi/PyDecorator.java
+++ b/python/src/com/jetbrains/python/psi/PyDecorator.java
@@ -7,10 +7,14 @@ import org.jetbrains.annotations.Nullable;
/**
* Covers a decorator call, e.g. @staticmethod.
* Decorators happen contextually above the function definition, but are stored inside it for convenience.
+ * Note:
+ * In @foo form, PyCallExpression's methods are related to invocation of foo
+ * as decorator. In @foo(...) form, these very methods are related to the call that returns the decorator
+ * to be applied. In either case, they are related to an invocation of foo.
* User: dcheryasov
* Date: Sep 26, 2008
*/
-public interface PyDecorator extends /*PyElement*/ PyCallExpression, StubBasedPsiElement {
+public interface PyDecorator extends PyCallExpression, StubBasedPsiElement {
/**
* @return the function being decorated, or null.
*/
@@ -18,9 +22,14 @@ public interface PyDecorator extends /*PyElement*/ PyCallExpression, StubBasedPs
PyFunction getTarget();
/**
- * True if the annotating function is a builtin. Uses a stub, does not incur parsing, useful togeter with getName().
+ * True if the annotating function is a builtin, useful togeter with getName(). Implementation uses stub info.
* @see com.jetbrains.python.psi.PyElement#getName()
*/
boolean isBuiltin();
+ /**
+ * @return true if invocation has a form of @foo(...).
+ */
+ boolean hasArgumentList();
+
}
diff --git a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
index ac2ecb2fb286..45ed666c7a4a 100644
--- a/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
+++ b/python/src/com/jetbrains/python/psi/impl/PyDecoratorImpl.java
@@ -71,6 +71,10 @@ public class PyDecoratorImpl extends PyPresentableElementImpl i
}
}
+ public boolean hasArgumentList() {
+ return getNode().findChildByType(PyElementTypes.ARGUMENT_LIST) != null;
+ }
+
public PyExpression getCallee() {
try {
return (PyExpression)getFirstChild().getNextSibling(); // skip the @ before call
@@ -93,7 +97,12 @@ public class PyDecoratorImpl extends PyPresentableElementImpl i
}
public PyMarkedFunction resolveCallee() {
- return PyCallExpressionHelper.resolveCallee(this);
+ PyMarkedFunction callee = PyCallExpressionHelper.resolveCallee(this);
+ if (callee == null) return null;
+ if (! hasArgumentList()) {
+ callee.getFlags().add(Flag.IMPLICIT_FIRST_ARG); // NOTE: assumes mutability
+ }
+ return callee;
}
@Override
diff --git a/python/testData/psi/DecoratedFunction.py b/python/testData/psi/DecoratedFunction.py
new file mode 100644
index 000000000000..74e2f175864c
--- /dev/null
+++ b/python/testData/psi/DecoratedFunction.py
@@ -0,0 +1,5 @@
+@staticmethod
+@xmlize(node="foo")
+def foo():
+ pass
+
diff --git a/python/testData/psi/DecoratedFunction.txt b/python/testData/psi/DecoratedFunction.txt
new file mode 100644
index 000000000000..df8288f4f518
--- /dev/null
+++ b/python/testData/psi/DecoratedFunction.txt
@@ -0,0 +1,32 @@
+PyFile:DecoratedFunction.py
+ PyFunction
+ PyDecoratorList
+ PyDecorator: @staticmethod
+ PsiElement(Py:AT)('@')
+ PyReferenceExpression: staticmethod
+ PsiElement(Py:IDENTIFIER)('staticmethod')
+ PsiWhiteSpace('\n')
+ PyDecorator: @xmlize
+ PsiElement(Py:AT)('@')
+ PyReferenceExpression: xmlize
+ PsiElement(Py:IDENTIFIER)('xmlize')
+ PyArgumentList
+ PsiElement(Py:LPAR)('(')
+ PyKeywordArgumentImpl: node
+ PsiElement(Py:IDENTIFIER)('node')
+ PsiElement(Py:EQ)('=')
+ PyStringLiteralExpression: foo
+ PsiElement(Py:STRING_LITERAL)('"foo"')
+ PsiElement(Py:RPAR)(')')
+ PsiWhiteSpace('\n')
+ PsiElement(Py:DEF_KEYWORD)('def')
+ PsiWhiteSpace(' ')
+ PsiElement(Py:IDENTIFIER)('foo')
+ PyParameterList
+ PsiElement(Py:LPAR)('(')
+ PsiElement(Py:RPAR)(')')
+ PsiElement(Py:COLON)(':')
+ PsiWhiteSpace('\n ')
+ PyStatementList
+ PyPassStatement
+ PsiElement(Py:PASS_KEYWORD)('pass')
diff --git a/python/testData/resolve/callee/DecoCall.py b/python/testData/resolve/callee/DecoCall.py
new file mode 100644
index 000000000000..01b73140e232
--- /dev/null
+++ b/python/testData/resolve/callee/DecoCall.py
@@ -0,0 +1,6 @@
+def deco(fun):
+ return fun
+
+@[deco
+def foo():
+ pass
diff --git a/python/testData/resolve/callee/DecoParamCall.py b/python/testData/resolve/callee/DecoParamCall.py
new file mode 100644
index 000000000000..ac94593a3b7e
--- /dev/null
+++ b/python/testData/resolve/callee/DecoParamCall.py
@@ -0,0 +1,11 @@
+def deco(prefix):
+ def fun(f):
+ print f
+ def dfun():
+ return [prefix, f()]
+ return dfun
+ return fun
+
+@][deco(1)
+def foo():
+ pass
diff --git a/python/testData/resolve/decorators/DecoCall.py b/python/testData/resolve/decorators/DecoCall.py
new file mode 100644
index 000000000000..54d9a78a561d
--- /dev/null
+++ b/python/testData/resolve/decorators/DecoCall.py
@@ -0,0 +1,8 @@
+def deco(fun):
+ return fun
+
+@][deco
+def foo():
+ pass
+
+# same as in callee test
diff --git a/python/testData/resolve/decorators/DecoParamCall.py b/python/testData/resolve/decorators/DecoParamCall.py
new file mode 100644
index 000000000000..8ae9ea0cd23c
--- /dev/null
+++ b/python/testData/resolve/decorators/DecoParamCall.py
@@ -0,0 +1,13 @@
+def deco(prefix):
+ def fun(f):
+ print f
+ def dfun():
+ return [prefix, f()]
+ return dfun
+ return fun
+
+@][deco(1)
+def foo():
+ pass
+
+# same as in callee test
diff --git a/python/testData/stubs/StubStructure.py b/python/testData/stubs/StubStructure.py
index 46e0738528c7..7e55fedf3aa8 100644
--- a/python/testData/stubs/StubStructure.py
+++ b/python/testData/stubs/StubStructure.py
@@ -1,9 +1,12 @@
+def deco(fun):
+ return fun # valid
+
class FooClass:
staticField = 1
def __init__(self):
self.instanceField = 2
- @staticmethod
+ @deco
def fooFunction(fooParam1, fooParam2) :
pass
diff --git a/python/testSrc/com/jetbrains/python/PyDecoratorTest.java b/python/testSrc/com/jetbrains/python/PyDecoratorTest.java
new file mode 100644
index 000000000000..625739b1f0c6
--- /dev/null
+++ b/python/testSrc/com/jetbrains/python/PyDecoratorTest.java
@@ -0,0 +1,54 @@
+package com.jetbrains.python;
+
+import com.intellij.openapi.application.PathManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.testFramework.ResolveTestCase;
+import com.jetbrains.python.psi.PyArgumentList;
+import com.jetbrains.python.psi.PyDecorator;
+import com.jetbrains.python.psi.PyFunction;
+import com.jetbrains.python.psi.PyExpression;
+
+/**
+ * Decorator-specific tests.
+ * User: dcheryasov
+ * Date: Dec 28, 2008 3:50:23 AM
+ */
+public class PyDecoratorTest extends ResolveTestCase {
+ private PsiElement find() throws Exception {
+ PsiReference ref = configureByFile(getTestName(false) + ".py");
+ return ref.getElement();
+ }
+
+ public void testDecoCall() throws Exception {
+ PsiElement targetElement = find().getParent();
+ assertTrue(targetElement instanceof PyDecorator);
+ PyDecorator deco = (PyDecorator)targetElement;
+ PyFunction decofun = deco.getTarget();
+ assertNotNull(decofun);
+ assertEquals("foo", decofun.getName());
+ assertFalse(deco.isBuiltin());
+ assertFalse(deco.hasArgumentList());
+ }
+
+ public void testDecoParamCall() throws Exception {
+ PsiElement targetElement = find().getParent();
+ assertTrue(targetElement instanceof PyDecorator);
+ PyDecorator deco = (PyDecorator)targetElement;
+ PyFunction decofun = deco.getTarget();
+ assertNotNull(decofun);
+ assertEquals("foo", decofun.getName());
+ assertFalse(deco.isBuiltin());
+ assertTrue(deco.hasArgumentList());
+ PyArgumentList arglist = deco.getArgumentList();
+ assertNotNull(arglist);
+ PyExpression[] args = arglist.getArguments();
+ assertEquals("argument count", 1, args.length);
+ assertEquals("argument value", "1", args[0].getText());
+ }
+
+ @Override
+ protected String getTestDataPath() {
+ return PathManager.getHomePath() + "/plugins/python/testData/resolve/decorators/";
+ }
+}
diff --git a/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java b/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java
index ceee3392504b..f3ff42165bfd 100644
--- a/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java
+++ b/python/testSrc/com/jetbrains/python/PyResolveCalleeTest.java
@@ -33,6 +33,18 @@ public class PyResolveCalleeTest extends ResolveTestCase {
assertTrue(resolved.getFlags().equals(EnumSet.noneOf(PyCallExpression.Flag.class)));
}
+ public void testDecoCall() throws Exception {
+ PyCallExpression.PyMarkedFunction resolved = resolveCallee();
+ assertNotNull(resolved.getFunction());
+ assertTrue(resolved.getFlags().equals(EnumSet.of(PyCallExpression.Flag.IMPLICIT_FIRST_ARG)));
+ }
+
+ public void testDecoParamCall() throws Exception {
+ PyCallExpression.PyMarkedFunction resolved = resolveCallee();
+ assertNotNull(resolved.getFunction());
+ assertTrue(resolved.getFlags().equals(EnumSet.noneOf(PyCallExpression.Flag.class)));
+ }
+
@Override
protected String getTestDataPath() {
return PathManager.getHomePath() + "/plugins/python/testData/resolve/callee/";
diff --git a/python/testSrc/com/jetbrains/python/PyStubsTest.java b/python/testSrc/com/jetbrains/python/PyStubsTest.java
index 9f2a39a3b8d7..1ab0a06c0a11 100644
--- a/python/testSrc/com/jetbrains/python/PyStubsTest.java
+++ b/python/testSrc/com/jetbrains/python/PyStubsTest.java
@@ -75,23 +75,20 @@ public class PyStubsTest extends CodeInsightTestCase {
assertNotParsed(file);
PyDecorator deco = da[0];
assertNotNull(deco);
- /*
- assertEquals(deco.getTarget(), decorated);
+ assertEquals("deco", deco.getName());
assertNotParsed(file);
- assertEquals(deco.getName(), "staticmethod");
- */
- assertEquals(deco.getName(), "staticmethod");
- assertNotParsed(file);
- //assertNotNull(deco.getCallee());
+ assertFalse(deco.isBuiltin());
final PyTargetExpression[] instanceAttrs = pyClass.getInstanceAttributes();
assertEquals(1, instanceAttrs.length);
assertEquals("instanceField", instanceAttrs [0].getName());
final List functions = file.getTopLevelFunctions();
- assertEquals(1, functions.size());
+ assertEquals(2, functions.size()); // "deco" and "topLevelFunction"
PyFunction func = functions.get(0);
+ assertEquals("deco", func.getName());
+ func = functions.get(1);
assertEquals("topLevelFunction", func.getName());
final List exprs = file.getTopLevelAttributes();
diff --git a/python/testSrc/com/jetbrains/python/PythonParsingTest.java b/python/testSrc/com/jetbrains/python/PythonParsingTest.java
index c91a934376bc..332d7efa575f 100644
--- a/python/testSrc/com/jetbrains/python/PythonParsingTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonParsingTest.java
@@ -77,4 +77,7 @@ public class PythonParsingTest extends ParsingTestCase {
doTest(true);
}
+ public void testDecoratedFunction() throws Exception {
+ doTest(true);
+ }
}
]