mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-53671 Don't treat methods qualified with a module as unbound
While resolving an aliased method exported from a module, we might lose the context that it was referenced via instance and hence its first "self" parameter is already bound and doesn't need to be passed explicitly. The reason is PyResolveUtil#doResolveQualifiedNameInScope (called in PyTargetExpressionImpl.multiResolveAssignedValue) performs resolve over qualified names saved in PSI stubs and returns plain PsiElements (end results) that don't retain such information about their qualifiers. QualifiedResolveResult can't be used there either because we don't keep PyExpressions in PSI stubs. What's more, when later such function is referenced via some module we consider it definitely unbound, even though a module cannot possibly have a method as its immediate attribute. I changed the logic so that we no longer consider referencing a method through a module as somehow affecting its bound/unbound state. GitOrigin-RevId: 17a6c3e5d43c088d0663ba54651004c8370d5eca
This commit is contained in:
committed by
intellij-monorepo-bot
parent
847cffdc6b
commit
63461fb262
+15
-11
@@ -10,6 +10,7 @@ import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.ThreeState;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.jetbrains.python.PyNames;
|
||||
@@ -486,25 +487,28 @@ public final class PyCallExpressionHelper {
|
||||
return true; // unqualified + method = implicit constructor call
|
||||
}
|
||||
for (PyExpression qualifier : qualifiers) {
|
||||
if (qualifier != null && isQualifiedByInstance(resolved, qualifier, context)) {
|
||||
return true;
|
||||
if (qualifier != null) {
|
||||
ThreeState byInstance = isQualifiedByInstance(resolved, qualifier, context);
|
||||
if (byInstance != ThreeState.UNSURE) {
|
||||
return byInstance.toBoolean();
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isQualifiedByInstance(@Nullable PyCallable resolved,
|
||||
@NotNull PyExpression qualifier,
|
||||
@NotNull TypeEvalContext context) {
|
||||
private static @NotNull ThreeState isQualifiedByInstance(@Nullable PyCallable resolved,
|
||||
@NotNull PyExpression qualifier,
|
||||
@NotNull TypeEvalContext context) {
|
||||
if (isQualifiedByClass(resolved, qualifier, context)) {
|
||||
return false;
|
||||
return ThreeState.NO;
|
||||
}
|
||||
final PyType qualifierType = context.getType(qualifier);
|
||||
if (qualifierType != null) {
|
||||
// TODO: handle UnionType
|
||||
if (qualifierType instanceof PyModuleType) return false; // qualified by module, not instance.
|
||||
// TODO: handle UnionType
|
||||
if (qualifierType instanceof PyModuleType) {
|
||||
return ThreeState.UNSURE;
|
||||
}
|
||||
return true; // NOTE. best guess: unknown qualifier is more probably an instance.
|
||||
return ThreeState.YES; // NOTE. best guess: unknown qualifier is more probably an instance.
|
||||
}
|
||||
|
||||
private static boolean isQualifiedByClass(@Nullable PyCallable resolved,
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from mod import func
|
||||
|
||||
func(1, 2)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
class C:
|
||||
def method(self, a: int, b: int) -> int: ...
|
||||
|
||||
_inst: C = ...
|
||||
func = _inst.method
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import mod
|
||||
|
||||
mod.func(1, 2)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
class C:
|
||||
def method(self, a: int, b: int) -> int: ...
|
||||
|
||||
_inst: C = ...
|
||||
func = _inst.method
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
import mod
|
||||
|
||||
mod.func(1, 2)
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
@staticmethod
|
||||
def method(a: int, b: int) -> int: ...
|
||||
|
||||
_inst: C = ...
|
||||
func = _inst.method
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
from mod import func
|
||||
|
||||
func(1, 2, <warning descr="Unexpected argument"><caret>3</warning>)
|
||||
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
def method(self, a, b):
|
||||
pass
|
||||
|
||||
_inst: C = C()
|
||||
func = _inst.method
|
||||
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
def method(self, a, b, i):
|
||||
pass
|
||||
|
||||
_inst: C = C()
|
||||
func = _inst.method
|
||||
@@ -0,0 +1,4 @@
|
||||
import mod
|
||||
|
||||
|
||||
mod.func(<arg>42)
|
||||
@@ -0,0 +1,7 @@
|
||||
class C:
|
||||
def method(self, param):
|
||||
pass
|
||||
|
||||
|
||||
_inst: C = C()
|
||||
func = _inst.method
|
||||
@@ -0,0 +1,4 @@
|
||||
import mod
|
||||
|
||||
|
||||
mod.func(<arg>42)
|
||||
@@ -0,0 +1,6 @@
|
||||
class C:
|
||||
def method(self, param):
|
||||
pass
|
||||
|
||||
|
||||
func = C.method
|
||||
@@ -311,6 +311,18 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
|
||||
feignCtrlP(marks.get("<arg2>").getTextOffset()).check("self: A, a, b", new String[]{"b"}, new String[]{"self: A, "});
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testUnboundMethodReassignedAndImportedWithQualifiedImport() {
|
||||
Map<String, PsiElement> marks = loadMultiFileTest(1);
|
||||
feignCtrlP(marks.get("<arg>").getTextOffset()).check("self: C, param", new String[]{"self: C, "}, ArrayUtil.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testBoundMethodReassignedAndImportedWithQualifiedImport() {
|
||||
Map<String, PsiElement> marks = loadMultiFileTest(1);
|
||||
feignCtrlP(marks.get("<arg>").getTextOffset()).check("self: C, param", new String[]{"param"}, new String[]{"self: C, "});
|
||||
}
|
||||
|
||||
public void testConstructorFactory() {
|
||||
Map<String, PsiElement> marks = loadTest(1);
|
||||
|
||||
|
||||
@@ -747,6 +747,13 @@ public class PyQuickFixTest extends PyTestCase {
|
||||
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(i1, <b>i</b>, <b>i3</b>, <b>num</b>)</html>", true, true);
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testChangeSignatureOfExportedBoundMethod() {
|
||||
runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
|
||||
doMultiFilesInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of method(self, a, b, <b>i</b>)</html>", "mod.py");
|
||||
});
|
||||
}
|
||||
|
||||
// PY-8174
|
||||
public void testChangeSignatureParametersDefaultValues() {
|
||||
doInspectionTest(PyArgumentListInspection.class, "<html>Change the signature of func(<b>i</b>, <b>foo</b>)</html>", true, true);
|
||||
|
||||
@@ -52,4 +52,26 @@ public class Py3ArgumentListInspectionTest extends PyInspectionTestCase {
|
||||
"a = A(<warning descr=\"Parameter 'x' unfilled\">)</warning>\n" +
|
||||
"b = B(<warning descr=\"Parameter 'x' unfilled\">)</warning>");
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testBoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testBoundMethodExportedAsTopLevelFunctionImportedWithFromImport() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testStaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport() {
|
||||
doMultiFileTest();
|
||||
}
|
||||
|
||||
// PY-53671
|
||||
public void testRandomRandint() {
|
||||
doTestByText("import random\n" +
|
||||
"\n" +
|
||||
"random.randint(1, 2)");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user