diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
index 8d59d75df9fa..50201cdceffa 100644
--- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
+++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java
@@ -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,
diff --git a/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithFromImport/a.py b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithFromImport/a.py
new file mode 100644
index 000000000000..9c5fa2502320
--- /dev/null
+++ b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithFromImport/a.py
@@ -0,0 +1,3 @@
+from mod import func
+
+func(1, 2)
diff --git a/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithFromImport/mod.pyi b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithFromImport/mod.pyi
new file mode 100644
index 000000000000..d91d087fb4f2
--- /dev/null
+++ b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithFromImport/mod.pyi
@@ -0,0 +1,5 @@
+class C:
+ def method(self, a: int, b: int) -> int: ...
+
+_inst: C = ...
+func = _inst.method
\ No newline at end of file
diff --git a/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/a.py b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/a.py
new file mode 100644
index 000000000000..c66aa063d76c
--- /dev/null
+++ b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/a.py
@@ -0,0 +1,3 @@
+import mod
+
+mod.func(1, 2)
\ No newline at end of file
diff --git a/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/mod.pyi b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/mod.pyi
new file mode 100644
index 000000000000..d91d087fb4f2
--- /dev/null
+++ b/python/testData/inspections/PyArgumentListInspection/BoundMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/mod.pyi
@@ -0,0 +1,5 @@
+class C:
+ def method(self, a: int, b: int) -> int: ...
+
+_inst: C = ...
+func = _inst.method
\ No newline at end of file
diff --git a/python/testData/inspections/PyArgumentListInspection/StaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/a.py b/python/testData/inspections/PyArgumentListInspection/StaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/a.py
new file mode 100644
index 000000000000..c66aa063d76c
--- /dev/null
+++ b/python/testData/inspections/PyArgumentListInspection/StaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/a.py
@@ -0,0 +1,3 @@
+import mod
+
+mod.func(1, 2)
\ No newline at end of file
diff --git a/python/testData/inspections/PyArgumentListInspection/StaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/mod.pyi b/python/testData/inspections/PyArgumentListInspection/StaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/mod.pyi
new file mode 100644
index 000000000000..690e7e972bc7
--- /dev/null
+++ b/python/testData/inspections/PyArgumentListInspection/StaticMethodExportedAsTopLevelFunctionImportedWithQualifiedImport/mod.pyi
@@ -0,0 +1,6 @@
+class C:
+ @staticmethod
+ def method(a: int, b: int) -> int: ...
+
+_inst: C = ...
+func = _inst.method
diff --git a/python/testData/inspections/changeSignatureOfExportedBoundMethod/changeSignatureOfExportedBoundMethod.py b/python/testData/inspections/changeSignatureOfExportedBoundMethod/changeSignatureOfExportedBoundMethod.py
new file mode 100644
index 000000000000..4757445c5264
--- /dev/null
+++ b/python/testData/inspections/changeSignatureOfExportedBoundMethod/changeSignatureOfExportedBoundMethod.py
@@ -0,0 +1,3 @@
+from mod import func
+
+func(1, 2, 3)
\ No newline at end of file
diff --git a/python/testData/inspections/changeSignatureOfExportedBoundMethod/mod.py b/python/testData/inspections/changeSignatureOfExportedBoundMethod/mod.py
new file mode 100644
index 000000000000..a3b7d022b6f6
--- /dev/null
+++ b/python/testData/inspections/changeSignatureOfExportedBoundMethod/mod.py
@@ -0,0 +1,6 @@
+class C:
+ def method(self, a, b):
+ pass
+
+_inst: C = C()
+func = _inst.method
\ No newline at end of file
diff --git a/python/testData/inspections/changeSignatureOfExportedBoundMethod/mod_after.py b/python/testData/inspections/changeSignatureOfExportedBoundMethod/mod_after.py
new file mode 100644
index 000000000000..0e9bd16c1026
--- /dev/null
+++ b/python/testData/inspections/changeSignatureOfExportedBoundMethod/mod_after.py
@@ -0,0 +1,6 @@
+class C:
+ def method(self, a, b, i):
+ pass
+
+_inst: C = C()
+func = _inst.method
\ No newline at end of file
diff --git a/python/testData/paramInfo/BoundMethodReassignedAndImportedWithQualifiedImport/a.py b/python/testData/paramInfo/BoundMethodReassignedAndImportedWithQualifiedImport/a.py
new file mode 100644
index 000000000000..ff45bddfdb33
--- /dev/null
+++ b/python/testData/paramInfo/BoundMethodReassignedAndImportedWithQualifiedImport/a.py
@@ -0,0 +1,4 @@
+import mod
+
+
+mod.func(42)
\ No newline at end of file
diff --git a/python/testData/paramInfo/BoundMethodReassignedAndImportedWithQualifiedImport/mod.py b/python/testData/paramInfo/BoundMethodReassignedAndImportedWithQualifiedImport/mod.py
new file mode 100644
index 000000000000..4df3d92d0ae0
--- /dev/null
+++ b/python/testData/paramInfo/BoundMethodReassignedAndImportedWithQualifiedImport/mod.py
@@ -0,0 +1,7 @@
+class C:
+ def method(self, param):
+ pass
+
+
+_inst: C = C()
+func = _inst.method
diff --git a/python/testData/paramInfo/UnboundMethodReassignedAndImportedWithQualifiedImport/a.py b/python/testData/paramInfo/UnboundMethodReassignedAndImportedWithQualifiedImport/a.py
new file mode 100644
index 000000000000..ff45bddfdb33
--- /dev/null
+++ b/python/testData/paramInfo/UnboundMethodReassignedAndImportedWithQualifiedImport/a.py
@@ -0,0 +1,4 @@
+import mod
+
+
+mod.func(42)
\ No newline at end of file
diff --git a/python/testData/paramInfo/UnboundMethodReassignedAndImportedWithQualifiedImport/mod.py b/python/testData/paramInfo/UnboundMethodReassignedAndImportedWithQualifiedImport/mod.py
new file mode 100644
index 000000000000..644f81777a92
--- /dev/null
+++ b/python/testData/paramInfo/UnboundMethodReassignedAndImportedWithQualifiedImport/mod.py
@@ -0,0 +1,6 @@
+class C:
+ def method(self, param):
+ pass
+
+
+func = C.method
diff --git a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java
index 559c8395e49b..149ce4a771a9 100644
--- a/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java
+++ b/python/testSrc/com/jetbrains/python/PyParameterInfoTest.java
@@ -311,6 +311,18 @@ public class PyParameterInfoTest extends LightMarkedTestCase {
feignCtrlP(marks.get("").getTextOffset()).check("self: A, a, b", new String[]{"b"}, new String[]{"self: A, "});
}
+ // PY-53671
+ public void testUnboundMethodReassignedAndImportedWithQualifiedImport() {
+ Map marks = loadMultiFileTest(1);
+ feignCtrlP(marks.get("").getTextOffset()).check("self: C, param", new String[]{"self: C, "}, ArrayUtil.EMPTY_STRING_ARRAY);
+ }
+
+ // PY-53671
+ public void testBoundMethodReassignedAndImportedWithQualifiedImport() {
+ Map marks = loadMultiFileTest(1);
+ feignCtrlP(marks.get("").getTextOffset()).check("self: C, param", new String[]{"param"}, new String[]{"self: C, "});
+ }
+
public void testConstructorFactory() {
Map marks = loadTest(1);
diff --git a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
index 97b9a05be626..9d57240451cc 100644
--- a/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
+++ b/python/testSrc/com/jetbrains/python/PyQuickFixTest.java
@@ -747,6 +747,13 @@ public class PyQuickFixTest extends PyTestCase {
doInspectionTest(PyArgumentListInspection.class, "Change the signature of func(i1, i, i3, num)", true, true);
}
+ // PY-53671
+ public void testChangeSignatureOfExportedBoundMethod() {
+ runWithLanguageLevel(LanguageLevel.getLatest(), () -> {
+ doMultiFilesInspectionTest(PyArgumentListInspection.class, "Change the signature of method(self, a, b, i)", "mod.py");
+ });
+ }
+
// PY-8174
public void testChangeSignatureParametersDefaultValues() {
doInspectionTest(PyArgumentListInspection.class, "Change the signature of func(i, foo)", true, true);
diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3ArgumentListInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3ArgumentListInspectionTest.java
index 6c913772e4c3..68be755d346d 100644
--- a/python/testSrc/com/jetbrains/python/inspections/Py3ArgumentListInspectionTest.java
+++ b/python/testSrc/com/jetbrains/python/inspections/Py3ArgumentListInspectionTest.java
@@ -52,4 +52,26 @@ public class Py3ArgumentListInspectionTest extends PyInspectionTestCase {
"a = A()\n" +
"b = B()");
}
+
+ // 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)");
+ }
}