Use parameters provided by function type for method signature checks in inspections

This commit is contained in:
Andrey Vlasovskikh
2013-09-13 19:22:18 +04:00
parent 79395feed7
commit 51d3a547c8
8 changed files with 104 additions and 84 deletions
@@ -39,14 +39,6 @@ public interface PyParameterList extends PyElement, StubBasedPsiElement<PyParame
*/
boolean hasKeywordContainer();
/**
* Checks is this parameter list is the same or is a superset of another parameter list.
* (The reverse is only true is the lists are the same.)
* @param another what to compare to
* @return true if this list is a superset of another.
*/
boolean isCompatibleTo(@NotNull PyParameterList another);
String getPresentableText(boolean includeDefaultValue);
@Nullable
@@ -7,7 +7,7 @@ import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyParameterList;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -46,13 +46,11 @@ public class PyInitNewSignatureInspection extends PyInspection {
String the_other_name = PyNames.NEW.equals(init_or_new.getName()) ? PyNames.INIT : PyNames.NEW;
PyFunction the_other = cls.findMethodByName(the_other_name, true);
if (the_other == null || builtins.getClass("object") == the_other.getContainingClass()) return;
final PyParameterList closer_list = init_or_new.getParameterList();
final PyParameterList farther_list = the_other.getParameterList();
if (! farther_list.isCompatibleTo(closer_list) &&
! closer_list.isCompatibleTo(farther_list) &&
closer_list.getContainingFile() == cls.getContainingFile()
if (!PyUtil.isSignatureCompatibleTo(the_other, init_or_new, myTypeEvalContext) &&
!PyUtil.isSignatureCompatibleTo(init_or_new, the_other, myTypeEvalContext) &&
init_or_new.getContainingFile() == cls.getContainingFile()
) {
registerProblem(closer_list, PyNames.NEW.equals(init_or_new.getName()) ?
registerProblem(init_or_new.getParameterList(), PyNames.NEW.equals(init_or_new.getName()) ?
PyBundle.message("INSP.new.incompatible.to.init") :
PyBundle.message("INSP.init.incompatible.to.new")
);
@@ -8,6 +8,7 @@ import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -48,7 +49,7 @@ public class PyMethodOverridingInspection extends PyInspection {
if (psiElement instanceof PyFunction) {
final PyFunction baseMethod = (PyFunction)psiElement;
final PyClass baseClass = baseMethod.getContainingClass();
if (!function.getParameterList().isCompatibleTo(baseMethod.getParameterList())) {
if (!PyUtil.isSignatureCompatibleTo(function, baseMethod, myTypeEvalContext)) {
final String msg = PyBundle.message("INSP.signature.mismatch",
cls.getName() + "." + name + "()",
baseClass != null ? baseClass.getName() : "");
@@ -57,8 +57,8 @@ public class PyPropertyDefinitionInspection extends PyInspection {
private LanguageLevel myLevel;
private List<PyClass> myStringClasses;
private PyParameterList myOneParamList;
private PyParameterList myTwoParamList; // arglist with two args, 'self' and 'value'
private PyFunction myOneParamFunction;
private PyFunction myTwoParamFunction; // arglist with two args, 'self' and 'value'
public Visitor(final ProblemsHolder holder, LocalInspectionToolSession session) {
super(holder, session);
@@ -83,9 +83,9 @@ public class PyPropertyDefinitionInspection extends PyInspection {
PyClass object_class = builtins.getClass("object");
if (object_class != null) {
final PyFunction method_repr = object_class.findMethodByName("__repr__", false);
if (method_repr != null) myOneParamList = method_repr.getParameterList();
if (method_repr != null) myOneParamFunction = method_repr;
final PyFunction method_delattr = object_class.findMethodByName("__delattr__", false);
if (method_delattr != null) myTwoParamList = method_delattr.getParameterList();
if (method_delattr != null) myTwoParamFunction = method_delattr;
}
}
@@ -235,8 +235,7 @@ public class PyPropertyDefinitionInspection extends PyInspection {
if (callable != null) {
// signature: at least two params, more optionals ok; first arg 'self'
final PyParameterList param_list = callable.getParameterList();
final PyParameterList two_parameters_list = myTwoParamList;
if (two_parameters_list != null && !param_list.isCompatibleTo(two_parameters_list)) {
if (myTwoParamFunction != null && !PyUtil.isSignatureCompatibleTo(callable, myTwoParamFunction, myTypeEvalContext)) {
registerProblem(being_checked, PyBundle.message("INSP.setter.signature.advice"));
}
checkForSelf(param_list);
@@ -254,8 +253,7 @@ public class PyPropertyDefinitionInspection extends PyInspection {
private void checkOneParameter(Callable callable, PsiElement being_checked, boolean is_getter) {
final PyParameterList param_list = callable.getParameterList();
final PyParameterList one_parameter_list = myOneParamList;
if (one_parameter_list != null && ! param_list.isCompatibleTo(one_parameter_list)) {
if (myOneParamFunction != null && !PyUtil.isSignatureCompatibleTo(callable, myOneParamFunction, myTypeEvalContext)) {
if (is_getter) registerProblem(being_checked, PyBundle.message("INSP.getter.signature.advice"));
else registerProblem(being_checked, PyBundle.message("INSP.deleter.signature.advice"));
}
@@ -1298,4 +1298,77 @@ public class PyUtil {
}
return Arrays.asList(callable.getParameterList().getParameters());
}
public static boolean isSignatureCompatibleTo(@NotNull Callable callable, @NotNull Callable otherCallable,
@NotNull TypeEvalContext context) {
final List<PyParameter> parameters = getParameters(callable, context);
final List<PyParameter> otherParameters = getParameters(otherCallable, context);
final int optionalCount = optionalParametersCount(parameters);
final int otherOptionalCount = optionalParametersCount(otherParameters);
final int requiredCount = requiredParametersCount(callable, parameters);
final int otherRequiredCount = requiredParametersCount(otherCallable, otherParameters);
if (hasPositionalContainer(otherParameters) || hasKeywordContainer(otherParameters)) {
if (otherParameters.size() == specialParametersCount(otherCallable, otherParameters)) {
return true;
}
}
if (hasPositionalContainer(parameters) || hasKeywordContainer(parameters)) {
return requiredCount <= otherRequiredCount;
}
return requiredCount <= otherRequiredCount && parameters.size() >= otherParameters.size() && optionalCount >= otherOptionalCount;
}
private static int optionalParametersCount(@NotNull List<PyParameter> parameters) {
int n = 0;
for (PyParameter parameter : parameters) {
if (parameter.getDefaultValue() != null) {
n++;
}
}
return n;
}
private static int requiredParametersCount(@NotNull Callable callable, @NotNull List<PyParameter> parameters) {
return parameters.size() - optionalParametersCount(parameters) - specialParametersCount(callable, parameters);
}
private static int specialParametersCount(@NotNull Callable callable, @NotNull List<PyParameter> parameters) {
int n = 0;
if (hasPositionalContainer(parameters)) {
n++;
}
if (hasKeywordContainer(parameters)) {
n++;
}
if (callable.asMethod() != null) {
n++;
}
else {
if (parameters.size() > 0) {
final PyParameter first = parameters.get(0);
if (PyNames.CANONICAL_SELF.equals(first.getName())) {
n++;
}
}
}
return n;
}
private static boolean hasPositionalContainer(@NotNull List<PyParameter> parameters) {
for (PyParameter parameter : parameters) {
if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isPositionalContainer()) {
return true;
}
}
return false;
}
private static boolean hasKeywordContainer(@NotNull List<PyParameter> parameters) {
for (PyParameter parameter : parameters) {
if (parameter instanceof PyNamedParameter && ((PyNamedParameter)parameter).isKeywordContainer()) {
return true;
}
}
return false;
}
}
@@ -5,7 +5,6 @@ import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.stubs.IStubElementType;
import com.jetbrains.python.PyElementTypes;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
import com.jetbrains.python.psi.*;
@@ -85,65 +84,6 @@ public class PyParameterListImpl extends PyBaseElementImpl<PyParameterListStub>
return false;
}
public boolean isCompatibleTo(@NotNull PyParameterList other) {
PyParameter[] params = getParameters();
final PyParameter[] otherParams = other.getParameters();
final int optionalCount = optionalParametersCount(params);
final int otherOptionalCount = optionalParametersCount(otherParams);
final int requiredCount = requiredParametersCount(this);
final int otherRequiredCount = requiredParametersCount(other);
if (other.hasPositionalContainer() || other.hasKeywordContainer()) {
if (otherParams.length == specialParametersCount(other)) {
return true;
}
}
if (hasPositionalContainer() || hasKeywordContainer()) {
return requiredCount <= otherRequiredCount;
}
return requiredCount <= otherRequiredCount && params.length >= otherParams.length && optionalCount >= otherOptionalCount;
}
private static int optionalParametersCount(@NotNull PyParameter[] parameters) {
int n = 0;
for (PyParameter parameter : parameters) {
if (parameter.getDefaultValue() != null) {
n++;
}
}
return n;
}
private static int specialParametersCount(@NotNull PyParameterList parameterList) {
int n = 0;
if (parameterList.hasPositionalContainer()) {
n++;
}
if (parameterList.hasKeywordContainer()) {
n++;
}
final PyFunction function = parameterList.getContainingFunction();
if (function != null) {
if (function.asMethod() != null) {
n++;
}
}
else {
final PyParameter[] parameters = parameterList.getParameters();
if (parameters.length > 0) {
final PyParameter first = parameters[0];
if (PyNames.CANONICAL_SELF.equals(first.getName())) {
n++;
}
}
}
return n;
}
private static int requiredParametersCount(@NotNull PyParameterList parameterList) {
final PyParameter[] parameters = parameterList.getParameters();
return parameters.length - optionalParametersCount(parameters) - specialParametersCount(parameterList);
}
@Override
@Nullable
public PyNamedParameter findParameterByName(@NotNull final String name) {
@@ -0,0 +1,13 @@
class MyType1(type):
def __instancecheck__(cls, instance):
return True
class MyType2(type):
def __instancecheck__<warning descr="Signature of method 'MyType2.__instancecheck__()' does not match signature of base method in class 'type'">(cls)</warning>:
return True
class MyType3(type):
def __instancecheck__<warning descr="Signature of method 'MyType3.__instancecheck__()' does not match signature of base method in class 'type'">(cls, foo, bar)</warning>:
return True
@@ -64,6 +64,11 @@ public class PyMethodOverridingInspectionTest extends PyTestCase {
doTest();
}
// PY-10229
public void testInstanceCheck() {
doTest();
}
private void doTest() {
myFixture.configureByFile(TEST_DIRECTORY + getTestName(false) + ".py");
myFixture.enableInspections(PyMethodOverridingInspection.class);