mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Use parameters provided by function type for method signature checks in inspections
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user