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); + } }