Improve PyKnownDecoratorUtil to support more decorator side-effects (PY-26023)

This commit is contained in:
Semyon Proshev
2017-11-20 17:26:46 +03:00
parent 31a8675b98
commit 9941c8e40a
8 changed files with 39 additions and 54 deletions
@@ -118,7 +118,7 @@ public class PyArgumentListInspection extends PyInspection {
final PyFunction function = (PyFunction)callable;
// Decorate functions may have different parameter lists. We don't match arguments with parameters of decorators yet
if (PyKnownDecoratorUtil.hasNonBuiltinDecorator(function, context) ||
if (PyKnownDecoratorUtil.hasUnknownOrChangingSignatureDecorator(function, context) ||
decoratedClassInitCall(call.getCallee(), function, context)) {
return;
}
@@ -144,7 +144,9 @@ public class PyArgumentListInspection extends PyInspection {
return Arrays
.stream(classReference.multiResolve(false))
.map(ResolveResult::getElement)
.anyMatch(element -> element instanceof PyClass && PyKnownDecoratorUtil.hasNonBuiltinDecorator((PyClass)element, context));
.anyMatch(
element -> element instanceof PyClass && PyKnownDecoratorUtil.hasUnknownOrChangingReturnTypeDecorator((PyClass)element, context)
);
}
return false;
@@ -68,7 +68,9 @@ public class PyNoneFunctionAssignmentInspection extends PyInspection {
if (callable instanceof PyFunction) {
final PyFunction function = (PyFunction)callable;
// Currently we don't infer types returned by decorators
if (hasInheritors(function) || PyKnownDecoratorUtil.hasNonBuiltinDecorator(function, myTypeEvalContext)) {
if (hasInheritors(function) ||
PyKnownDecoratorUtil.hasUnknownOrChangingReturnTypeDecorator(function, myTypeEvalContext) ||
PyKnownDecoratorUtil.hasAbstractDecorator(function, myTypeEvalContext)) {
return true;
}
}
@@ -86,6 +86,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
return (PyUnresolvedReferencesInspection)inspectionProfile.getUnwrappedTool(SHORT_NAME_KEY.toString(), element);
}
@Override
@Nls
@NotNull
public String getDisplayName() {
@@ -805,7 +806,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection {
}
if (type instanceof PyFunctionTypeImpl) {
final PyCallable callable = ((PyFunctionTypeImpl)type).getCallable();
if (callable instanceof PyFunction && PyKnownDecoratorUtil.hasNonBuiltinDecorator((PyFunction)callable, myTypeEvalContext)) {
if (callable instanceof PyFunction && PyKnownDecoratorUtil.hasUnknownDecorator((PyFunction)callable, myTypeEvalContext)) {
return true;
}
}
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi;
import com.intellij.psi.PsiElement;
@@ -100,7 +86,6 @@ public class PyKnownDecoratorUtil {
}
}
private static final Set<KnownDecorator> BUILTIN_DECORATORS = EnumSet.of(PROPERTY, CLASSMETHOD, STATICMETHOD, TYPING_OVERLOAD);
private static final Set<KnownDecorator> ABSTRACT_DECORATORS = EnumSet.of(ABC_ABSTRACTMETHOD,
ABC_ABSTRACTPROPERTY,
ABC_ABSTRACTSTATICMETHOD,
@@ -192,23 +177,6 @@ public class PyKnownDecoratorUtil {
return !allDecoratorsAreKnown(element, getKnownDecorators(element, context));
}
/**
* Checks that given element has any non-builtin decorators.
*
* @param element decoratable element to check
* @param context type evaluation context. If it doesn't allow switch to AST, decorators will be compared by the text of the last component
* of theirs qualified names.
* @see PyKnownDecoratorUtil.KnownDecorator
*/
public static boolean hasNonBuiltinDecorator(@NotNull PyDecoratable element, @NotNull TypeEvalContext context) {
final List<KnownDecorator> knownDecorators = getKnownDecorators(element, context);
if (!allDecoratorsAreKnown(element, knownDecorators)) {
return true;
}
knownDecorators.removeAll(BUILTIN_DECORATORS);
return !knownDecorators.isEmpty();
}
/**
* Checks that given function has any decorators from {@code abc} module.
*
@@ -238,6 +206,16 @@ public class PyKnownDecoratorUtil {
return getKnownDecorators(function, context).contains(TYPING_OVERLOAD);
}
public static boolean hasUnknownOrChangingSignatureDecorator(@NotNull PyDecoratable decoratable, @NotNull TypeEvalContext context) {
final List<KnownDecorator> decorators = getKnownDecorators(decoratable, context);
return !allDecoratorsAreKnown(decoratable, decorators) || decorators.contains(UNITTEST_MOCK_PATCH);
}
public static boolean hasUnknownOrChangingReturnTypeDecorator(@NotNull PyDecoratable decoratable, @NotNull TypeEvalContext context) {
final List<KnownDecorator> decorators = getKnownDecorators(decoratable, context);
return !allDecoratorsAreKnown(decoratable, decorators) || decorators.contains(UNITTEST_MOCK_PATCH);
}
private static boolean allDecoratorsAreKnown(@NotNull PyDecoratable element, @NotNull List<KnownDecorator> decorators) {
final PyDecoratorList decoratorList = element.getDecoratorList();
return decoratorList == null ? decorators.isEmpty() : decoratorList.getDecorators().length == decorators.size();
@@ -69,7 +69,7 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
if (!mySuppressParentheses &&
element instanceof PyFunction && ((PyFunction)element).getProperty() == null &&
!PyKnownDecoratorUtil.hasNonBuiltinDecorator((PyFunction)element, context) &&
!PyKnownDecoratorUtil.hasUnknownDecorator((PyFunction)element, context) &&
!isSingleArgDecoratorCall(myContext, (PyFunction)element)) {
item = item.withInsertHandler(PyFunctionInsertHandler.INSTANCE);
final List<PyCallableParameter> parameters = ((PyFunction)element).getParameters(context);
@@ -371,7 +371,8 @@ public class PyTypeChecker {
}
if (type instanceof PyFunctionType) {
final PyCallable callable = ((PyFunctionType)type).getCallable();
if (callable instanceof PyDecoratable && PyKnownDecoratorUtil.hasUnknownDecorator((PyDecoratable)callable, context)){
if (callable instanceof PyDecoratable &&
PyKnownDecoratorUtil.hasUnknownOrChangingReturnTypeDecorator((PyDecoratable)callable, context)){
return true;
}
}
@@ -0,0 +1,10 @@
from abc import ABCMeta, abstractmethod
class IUserService(metaclass=ABCMeta):
@abstractmethod
def get_user_by_id(self, *, user_id):
pass
the_user_service = get_service(IUserService) # type: IUserService
user = the_user_service.get_user_by_id(<warning descr="Unexpected argument">123</warning><warning descr="Parameter 'user_id' unfilled">)</warning>
@@ -1,18 +1,4 @@
/*
* Copyright 2000-2017 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.
*/
// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.inspections;
import com.jetbrains.python.fixtures.PyInspectionTestCase;
@@ -327,4 +313,9 @@ public class PyArgumentListInspectionTest extends PyInspectionTestCase {
public void testKwargsAgainstKeywordOnly() {
runWithLanguageLevel(LanguageLevel.PYTHON36, this::doTest);
}
// PY-26023
public void testAbstractMethod() {
runWithLanguageLevel(LanguageLevel.PYTHON30, this::doTest);
}
}