Adds special handling of __new__ signature (PY-217).

This commit is contained in:
Dmitry Cheryasov
2010-03-31 07:17:20 +03:00
parent d7c05306b0
commit 1364124a30
7 changed files with 122 additions and 9 deletions
@@ -3,6 +3,7 @@ package com.jetbrains.python.psi;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.StubBasedPsiElement;
import com.intellij.util.Processor;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.stubs.PyClassStub;
import org.jetbrains.annotations.NonNls;
@@ -37,6 +38,24 @@ public interface PyClass extends PsiNamedElement, PyStatement, NameDefiner, PyDo
@Nullable
PyFunction findMethodByName(@NotNull @NonNls final String name, boolean inherited);
/**
* Finds either __init__ or __new__, whichever is defined for given class.
* If __init__ is defined, it is found first. This mimics the way initialization methods
* are searched for and called by Python when a constructor call is made.
* Since __new__ only makes sense for new-style classes, an old-style class never finds it with this method.
* @param inherited true: search in superclasses, too.
* @return a method that would be called first when an instance of this class is instantiated.
*/
@Nullable
PyFunction findInitOrNew(boolean inherited);
/**
* Apply a processor to every method, looking at superclasses in method resolution order as needed.
* @param processor what to apply
* @param inherited true: search in superclasses, too.
*/
void scanMethods(Processor<PyFunction> processor, boolean inherited);
PyTargetExpression[] getClassAttributes();
PyTargetExpression[] getInstanceAttributes();
@@ -45,7 +45,7 @@ public class PyCallExpressionHelper {
PsiElement redefining_func = refex.getReference().resolve();
if (redefining_func != null) {
PsiNamedElement true_func = PyBuiltinCache.getInstance(us).getByName(refname, PsiNamedElement.class);
if (true_func instanceof PyClass) true_func = ((PyClass)true_func).findMethodByName(PyNames.INIT, true);
if (true_func instanceof PyClass) true_func = ((PyClass)true_func).findInitOrNew(true);
if (true_func == redefining_func) {
// yes, really a case of "foo = classmethod(foo)"
PyArgumentList arglist = redefining_call.getArgumentList();
@@ -73,10 +73,14 @@ public class PyCallExpressionHelper {
public static PyCallExpression.PyMarkedFunction resolveCallee(PyCallExpression us) {
PyExpression callee = us.getCallee();
PyFunction.Flag wrapped_flag = null;
boolean is_constructor_call = false;
if (callee instanceof PyReferenceExpression) {
PyReferenceExpression ref = (PyReferenceExpression)callee;
PsiElement resolved = ref.followAssignmentsChain();
if (resolved instanceof PyClass) resolved = ((PyClass)resolved).findMethodByName(PyNames.INIT, true); // class to constructor call
if (resolved instanceof PyClass) {
resolved = ((PyClass)resolved).findInitOrNew(true); // class to constructor call
is_constructor_call = true;
}
else if (resolved instanceof PyCallExpression) {
// is it a case of "foo = classmethod(foo)"?
PyCallExpression redefining_call = (PyCallExpression)resolved;
@@ -91,6 +95,9 @@ public class PyCallExpressionHelper {
if (resolved instanceof PyFunction) {
EnumSet<PyFunction.Flag> flags = EnumSet.noneOf(PyFunction.Flag.class);
int implicit_offset = getImplicitArgumentCount(us.getCallee(), (PyFunction) resolved, wrapped_flag, flags);
if (! is_constructor_call && PyNames.NEW.equals(((PyFunction)resolved).getName())) {
implicit_offset = Math.min(implicit_offset-1, 0); // case of Class.__new__
}
return new PyCallExpression.PyMarkedFunction((PyFunction)resolved, flags, implicit_offset);
}
}
@@ -117,6 +124,7 @@ public class PyCallExpressionHelper {
if (wrapped_flag == PyFunction.Flag.STATICMETHOD && implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self'
if (wrapped_flag == PyFunction.Flag.CLASSMETHOD && ! is_by_instance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg
}
if (! is_by_instance && PyNames.NEW.equals(method.getName())) implicit_offset += 1; // constructor call
// decorators?
if (PyNames.INIT.equals(method.getName())) {
String refName = callReference instanceof PyReferenceExpression
@@ -139,7 +147,7 @@ public class PyCallExpressionHelper {
if (flags != null) {
flags.add(PyFunction.Flag.STATICMETHOD);
}
if (implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self'
if (is_by_instance && implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self'
}
else if (PyNames.CLASSMETHOD.equals(deconame)) {
if (flags != null) {
@@ -13,6 +13,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.reference.SoftReference;
import com.intellij.util.Icons;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
@@ -261,22 +262,59 @@ public class PyClassImpl extends PyPresentableElementImpl<PyClassStub> implement
return result.toArray(new PyFunction[result.size()]);
}
private static class NameFindingProcessor implements Processor<PyFunction> {
private PyFunction myResult;
private String[] myNames;
public NameFindingProcessor(String... names) {
myNames = names;
myResult = null;
}
public PyFunction getResult() {
return myResult;
}
public boolean process(PyFunction pyFunction) {
String fname = pyFunction.getName();
for (String name: myNames) {
if (name.equals(fname)) {
myResult = pyFunction;
return false;
}
}
return true;
}
}
public PyFunction findMethodByName(@NotNull final String name, boolean inherited) {
NameFindingProcessor proc = new NameFindingProcessor(name);
scanMethods(proc, inherited);
return proc.getResult();
}
@Nullable
public PyFunction findInitOrNew(boolean inherited) {
NameFindingProcessor proc;
if (isNewStyleClass()) proc = new NameFindingProcessor(PyNames.INIT, PyNames.NEW);
else proc = new NameFindingProcessor(PyNames.INIT);
scanMethods(proc, inherited);
return proc.getResult();
}
public void scanMethods(Processor<PyFunction> processor, boolean inherited) {
PyFunction[] methods = getMethods();
for(PyFunction method: methods) {
if (name.equals(method.getName())) {
return method;
}
if (! processor.process(method)) return;
}
if (inherited) {
for (PyClass ancestor : iterateAncestors()) {
PyFunction candidate = ancestor.findMethodByName(name, false); // not recursively, we want MRI in MI cases
if (candidate != null) return candidate;
ancestor.scanMethods(processor, false);
}
}
return null;
}
public PyTargetExpression[] getClassAttributes() {
PyClassStub stub = getStub();
if (stub != null) {
@@ -0,0 +1,10 @@
# makes sense for python 2.x
class A:
def __init__(self, one):
pass
class B(A):
def __new__(cls, one, two):
pass
b = B(<arg1>"only_one")
@@ -0,0 +1,7 @@
# signature of overridden __new__
class A(object):
def __new__(cls, a, b):
pass
A(<arg1>1, <arg2>2)
@@ -0,0 +1,7 @@
# signature of overridden __new__
class A(object):
def __new__(cls, a, b):
pass
A.__new__(<arg1>A, <arg2>1, <arg3>2)
@@ -247,6 +247,30 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self,a,b", new String[]{"b"}, new String[]{"self,"});
}
public void testRedefinedNewConstructorCall() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 2);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls,a,b", new String[]{"a,"}, new String[]{"cls,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls,a,b", new String[]{"b"}, new String[]{"cls,"});
}
public void testRedefinedNewDirectCall() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 3);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("cls,a,b", new String[]{"cls,"});
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("cls,a,b", new String[]{"a,"});
feignCtrlP(marks.get("<arg3>").getTextOffset()).check("cls,a,b", new String[]{"b"});
}
public void testIgnoreNewInOldStyleClass() throws Exception {
Map<String, PsiElement> marks = loadTest();
assertEquals("Test data sanity", marks.size(), 1);
feignCtrlP(marks.get("<arg1>").getTextOffset()).check("self,one", new String[]{"one"}, new String[]{"self,"});
}
// TODO: add method tests with decorators when a mock SDK is available
/**