blob: 9b0526881b27e08b3000b1a2a3ebffec4c03f39c [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.intellij.psi.util.PsiTreeUtil;
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.List;
import java.util.Map;
import static com.jetbrains.python.psi.PyFunction.Modifier.CLASSMETHOD;
import static com.jetbrains.python.psi.PyFunction.Modifier.STATICMETHOD;
/**
* Checks for for calls like <code>X.method(y,...)</code>, where y is not an instance of X.
* <br/>
* Not marked are cases of inheritance calls in old-style classes, like:<pre>
* class B(A):
* def foo(self):
* A.foo(self)
* </pre>
* <br/>
* 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.WEAK_WARNING;
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
return new Visitor(holder, session);
}
public 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();
final PyArgumentList arglist = call.getArgumentList();
if (arglist != null) {
CallArgumentsMapping analysis = arglist.analyzeCall(getResolveContext());
final PyCallExpression.PyMarkedCallee markedCallee = analysis.getMarkedCallee();
if (markedCallee != null && markedCallee.getModifier() != STATICMETHOD) {
final List<PyParameter> params = PyUtil.getParameters(markedCallee.getCallable(), myTypeEvalContext);
if (params.size() > 0 && params.get(0) instanceof PyNamedParameter) {
PyNamedParameter first_param = (PyNamedParameter)params.get(0);
for (Map.Entry<PyExpression, PyNamedParameter> 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.getModifier() != 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 != qual_class) {
// delegating to a parent is fine
if (markedCallee.getCallable() instanceof PyFunction) {
Callable callable = PsiTreeUtil.getParentOfType(call, Callable.class);
if (callable != null) {
PyFunction method = callable.asMethod();
if (method != null) {
PyClass calling_class = method.getContainingClass();
assert calling_class != null; // it's a method
if (first_arg_class.isSubclass(qual_class) && calling_class.isSubclass(qual_class)) {
break;
// TODO: might propose to switch to super() here
}
}
}
}
// otherwise, it's not
registerProblem(
first_arg,
PyBundle.message(
"INSP.passing.$0.instead.of.$1",
first_arg_class.getQualifiedName(), qual_class.getQualifiedName()
)
);
}
}
break; // once we found the first parameter, we don't need the rest
}
}
}
}
}
}
}
}
}
}
}
}