diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index b9062ce1555c..41e22026d1b2 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -270,6 +270,12 @@ INSP.setter.signature.advice=Setter signature should be (self, value)
INSP.deleter.signature.advice=Deleter signature should be (self)
INSP.accessor.first.param.is.$0=First parameter of an accessor is usually called ''{0}''
+# PyCallByClassInspection
+INSP.NAME.different.class.call=Calling a method by class using an instance of a different class
+INSP.instance.of.$0.excpected=An instance of {0} expected, not the class itself
+INSP.passing.$0.instead.of.$1=Passing {0} instead of {1}. Is this intentional?
+
+
# Refactoring
# introduce
refactoring.introduce.name.error=Incorrect name
diff --git a/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java b/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java
new file mode 100644
index 000000000000..097f48892dad
--- /dev/null
+++ b/python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java
@@ -0,0 +1,114 @@
+package com.jetbrains.python.inspections;
+
+import com.intellij.codeHighlighting.HighlightDisplayLevel;
+import com.intellij.codeInspection.LocalInspectionToolSession;
+import com.intellij.codeInspection.ProblemsHolder;
+import com.intellij.psi.PsiElementVisitor;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.psi.*;
+import com.jetbrains.python.psi.types.PyClassType;
+import com.jetbrains.python.psi.types.PyType;
+import org.jetbrains.annotations.Nls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
+
+import static com.jetbrains.python.psi.PyFunction.Flag.STATICMETHOD;
+import static com.jetbrains.python.psi.PyFunction.Flag.CLASSMETHOD;
+
+/**
+ * Checks for for calls like X.method(y,...), where y is not an instance of X.
+ *
+ * User: dcheryasov
+ * Date: Sep 22, 2010 12:21:44 PM
+ */
+public class PyCallByClassInspection extends PyInspection {
+ @Nls
+ @NotNull
+ @Override
+ public String getDisplayName() {
+ return PyBundle.message("INSP.NAME.different.class.call");
+ }
+
+ @Override
+ public boolean isEnabledByDefault() {
+ return true;
+ }
+
+ @NotNull
+ @Override
+ public HighlightDisplayLevel getDefaultLevel() {
+ return HighlightDisplayLevel.INFO;
+ }
+
+ @NotNull
+ @Override
+ public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, LocalInspectionToolSession session) {
+ return new Visitor(holder, session);
+ }
+
+ private static class Visitor extends PyInspectionVisitor {
+
+ public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) {
+ super(holder, session);
+ }
+
+
+ @Override
+ public void visitPyCallExpression(PyCallExpression call) {
+ PyExpression callee = call.getCallee();
+ if (callee instanceof PyQualifiedExpression) {
+ PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier();
+ if (qualifier != null) {
+ PyType qual_type = myTypeEvalContext.getType(qualifier);
+ if (qual_type instanceof PyClassType) {
+ final PyClassType qual_class_type = (PyClassType)qual_type;
+ if (qual_class_type.isDefinition()) {
+ PyClass qual_class = qual_class_type.getPyClass();
+ if (qual_class != null) {
+ final PyArgumentList arglist = call.getArgumentList();
+ if (arglist != null) {
+ PyArgumentList.AnalysisResult analysis = arglist.analyzeCall(myTypeEvalContext);
+ final PyCallExpression.PyMarkedCallee markedCallee = analysis.getMarkedCallee();
+ if (markedCallee != null && !markedCallee.getFlags().contains(STATICMETHOD)) {
+ PyParameter[] params = markedCallee.getCallable().getParameterList().getParameters();
+ if (params.length > 0 && params[0] instanceof PyNamedParameter) {
+ PyNamedParameter first_param = (PyNamedParameter)params[0];
+ for (Map.Entry entry : analysis.getPlainMappedParams().entrySet()) {
+ // we ignore *arg and **arg which we cannot analyze
+ if (entry.getValue() == first_param) {
+ PyExpression first_arg = entry.getKey();
+ assert first_arg != null;
+ PyType first_arg_type = myTypeEvalContext.getType(first_arg);
+ if (first_arg_type instanceof PyClassType) {
+ final PyClassType first_arg_class_type = (PyClassType)first_arg_type;
+ if (first_arg_class_type.isDefinition() && !markedCallee.getFlags().contains(CLASSMETHOD)) {
+ registerProblem(
+ first_arg,
+ PyBundle.message("INSP.instance.of.$0.excpected", qual_class.getQualifiedName())
+ );
+ }
+ PyClass first_arg_class = first_arg_class_type.getPyClass();
+ if (first_arg_class != null && first_arg_class != qual_class) {
+ registerProblem(
+ first_arg,
+ PyBundle.message(
+ "INSP.passing.$0.instead.of.$1",
+ first_arg_class.getQualifiedName(), qual_class.getQualifiedName()
+ )
+ );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java
index 25f69bb19edd..7f8f0a84236a 100644
--- a/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java
+++ b/python/src/com/jetbrains/python/inspections/PythonInspectionToolProvider.java
@@ -45,6 +45,7 @@ public class PythonInspectionToolProvider implements InspectionToolProvider {
PyPropertyDefinitionInspection.class,
PyInconsistentIndentationInspection.class,
PyNestedDecoratorsInspection.class,
+ PyCallByClassInspection.class,
};
}
}
diff --git a/python/testData/inspections/PyCallByClassInspection/expected.xml b/python/testData/inspections/PyCallByClassInspection/expected.xml
new file mode 100644
index 000000000000..cff4bf4b8cc4
--- /dev/null
+++ b/python/testData/inspections/PyCallByClassInspection/expected.xml
@@ -0,0 +1,19 @@
+
+
+
+ badarglist.py
+ 7
+ Passing src.badarglist.A instead of src.badarglist.Z. Is this intentional?
+
+
+ badarglist.py
+ 8
+ An instance of src.badarglist.Z is expected, not the class itself
+
+
+ badarglist.py
+ 9
+ An instance of src.badarglist.Z is expected, not the class itself
+ Passing src.badarglist.A instead of src.badarglist.Z. Is this intentional?
+
+
diff --git a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java
index 2dbdfb66441a..bd19aebe248b 100644
--- a/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonInspectionsTest.java
@@ -218,6 +218,10 @@ public class PythonInspectionsTest extends PyLightFixtureTestCase {
doTest(getTestName(false), inspection);
}
+ public void testPyCallByClassInspection() throws Exception {
+ doTest(getTestName(false), new PyCallByClassInspection());
+ }
+
public void testPyCallingNonCallableInspection() throws Exception {
doHighlightingTest(PyCallingNonCallableInspection.class, LanguageLevel.PYTHON26);
}