From 92bb78dff8495ad3f6d9ed304db112d0c1cc10f2 Mon Sep 17 00:00:00 2001 From: Dmitry Cheryasov Date: Wed, 22 Sep 2010 17:22:17 +0300 Subject: [PATCH] Partially closes PY-1438, adds call-by-class first argument type check. --- .../com/jetbrains/python/PyBundle.properties | 6 + .../inspections/PyCallByClassInspection.java | 114 ++++++++++++++++++ .../PythonInspectionToolProvider.java | 1 + .../PyCallByClassInspection/expected.xml | 19 +++ .../python/PythonInspectionsTest.java | 4 + 5 files changed, 144 insertions(+) create mode 100644 python/src/com/jetbrains/python/inspections/PyCallByClassInspection.java create mode 100644 python/testData/inspections/PyCallByClassInspection/expected.xml 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); }