mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Adds special handling of __new__ signature (PY-217).
This commit is contained in:
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user