mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
PY-34368: False warning "This decorator will not receive a callable it may expect" when @classmethod is not the last applied
- improve inspection to respect unaffected decorators wrt. classmethod/staticmethod - improve inspection message - add tests GitOrigin-RevId: 0eb431ae1aa2c40fa9056f7032936bb05bf8c32b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
836930bab0
commit
f35ff1b00a
@@ -587,7 +587,7 @@ INSP.first.param.must.not.be.tuple=First parameter of a non-static method must n
|
||||
|
||||
# PyNestedDecoratorsInspection
|
||||
INSP.NAME.nested.decorators=Problematic nesting of decorators
|
||||
INSP.decorator.receives.unexpected.builtin=This decorator will not receive a callable it may expect; the built-in decorator returns a special object
|
||||
INSP.decorator.receives.unexpected.builtin=This decorator will not receive the callable it may expect; the previously called decorator ''{0}'' returns a special object
|
||||
|
||||
# PyRedeclarationInspection
|
||||
INSP.NAME.redeclaration=Redeclared names without usages
|
||||
|
||||
+77
-16
@@ -8,13 +8,14 @@ import com.intellij.psi.PsiElementVisitor;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.PyPsiBundle;
|
||||
import com.jetbrains.python.inspections.quickfix.RemoveDecoratorQuickFix;
|
||||
import com.jetbrains.python.psi.PyDecorator;
|
||||
import com.jetbrains.python.psi.PyDecoratorList;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Checks nested decorators, especially whatever comes after @classmethod.
|
||||
* <br/>
|
||||
@@ -29,29 +30,89 @@ public final class PyNestedDecoratorsInspection extends PyInspection {
|
||||
}
|
||||
|
||||
public static class Visitor extends PyInspectionVisitor {
|
||||
private static final Set<String> TRANSFORMING_DECORATORS = Set.of(PyNames.CLASSMETHOD, PyNames.STATICMETHOD);
|
||||
private static final Set<String> UNAFFECTED_DECORATORS = Set.of(
|
||||
"typing.final", "typing.no_type_check", "typing.overload", "typing.override", "typing.type_check_only",
|
||||
"typing_extensions.final", "typing_extensions.no_type_check", "typing_extensions.overload", "typing_extensions.override", "typing_extensions.type_check_only",
|
||||
"functools.singledispatchmethod",
|
||||
"pydantic.functional_validators.field_validator", "pydantic.functional_validators.model_validator", "pydantic.class_validators.validator",
|
||||
"django.views.decorators.cache.cache_page",
|
||||
"tenacity.retry"
|
||||
);
|
||||
|
||||
|
||||
public Visitor(@Nullable ProblemsHolder holder, @NotNull TypeEvalContext context) {
|
||||
super(holder, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks decorators that are applied before a transforming decorator and might be affected by this.
|
||||
* A transforming decorator is one that returns other than a function.
|
||||
* A decorator might be affected if it is not excluded explicitly here.
|
||||
*
|
||||
* <pre>
|
||||
* \@mydeco # < -- warning here
|
||||
* \@classmethod
|
||||
* def foo(cls):
|
||||
* ...
|
||||
* </pre>
|
||||
*/
|
||||
@Override
|
||||
public void visitPyFunction(final @NotNull PyFunction node) {
|
||||
PyDecoratorList decolist = node.getDecoratorList();
|
||||
if (decolist != null) {
|
||||
PyDecorator[] decos = decolist.getDecorators();
|
||||
if (decos.length > 1) {
|
||||
for (int i = decos.length - 1; i >= 1; i -= 1) {
|
||||
PyDecorator deco = decos[i];
|
||||
String deconame = deco.getName();
|
||||
if ((PyNames.CLASSMETHOD.equals(deconame) || PyNames.STATICMETHOD.equals(deconame)) && deco.isBuiltin()) {
|
||||
registerProblem(
|
||||
decos[i-1],
|
||||
PyPsiBundle.message("INSP.decorator.receives.unexpected.builtin"),
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, null, new RemoveDecoratorQuickFix()
|
||||
);
|
||||
}
|
||||
if (decolist == null) {
|
||||
return;
|
||||
}
|
||||
PyDecorator[] decos = decolist.getDecorators();
|
||||
if (decos.length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = decos.length - 1; i >= 1; i -= 1) { // start at the innermost
|
||||
PyDecorator decoInner = decos[i];
|
||||
String decoInnerName = decoInner.getName();
|
||||
boolean isTransforming = TRANSFORMING_DECORATORS.contains(decoInnerName) && decoInner.isBuiltin();
|
||||
if (!isTransforming) {
|
||||
continue;
|
||||
}
|
||||
for (int j = i - 1; j >= 0; j -= 1) {
|
||||
PyDecorator decoOuter = decos[j];
|
||||
boolean maybeAffected = !isUnaffectedDecorator(decoOuter);
|
||||
if (maybeAffected) {
|
||||
registerProblem(
|
||||
decoOuter,
|
||||
PyPsiBundle.message("INSP.decorator.receives.unexpected.builtin", decoInnerName),
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, null, new RemoveDecoratorQuickFix()
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isUnaffectedDecorator(@NotNull PyDecorator decorator) {
|
||||
List<@NotNull PyCallable> pyCallables = decorator.multiResolveCalleeFunction(getResolveContext());
|
||||
for (PyCallable callable : pyCallables) {
|
||||
String decoOuterName = getQualifiedName(callable);
|
||||
if (UNAFFECTED_DECORATORS.contains(decoOuterName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @Nullable String getQualifiedName(PyCallable callable) {
|
||||
String decoOuterName = null;
|
||||
if (callable instanceof PyFunction pyFunction) {
|
||||
PyClass constrClass = PyUtil.turnConstructorIntoClass(pyFunction);
|
||||
if (constrClass != null) {
|
||||
decoOuterName = constrClass.getQualifiedName();
|
||||
}
|
||||
}
|
||||
if (decoOuterName == null) {
|
||||
decoOuterName = callable.getQualifiedName();
|
||||
}
|
||||
return decoOuterName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,32 @@
|
||||
<problems>
|
||||
<problem>
|
||||
<file>decorated.py</file>
|
||||
<line>25</line>
|
||||
<description>This decorator will not receive a callable it may expect; the built-in decorator returns a special object</description>
|
||||
<line>28</line>
|
||||
<description>This decorator will not receive the callable it may expect; the previously called decorator 'classmethod' returns a special object</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>decorated.py</file>
|
||||
<line>30</line>
|
||||
<description>This decorator will not receive a callable it may expect; the built-in decorator returns a special object</description>
|
||||
<line>33</line>
|
||||
<description>This decorator will not receive the callable it may expect; the previously called decorator 'staticmethod' returns a special object</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>decorated.py</file>
|
||||
<line>35</line>
|
||||
<description>This decorator will not receive a callable it may expect; the built-in decorator returns a special object</description>
|
||||
<line>38</line>
|
||||
<description>This decorator will not receive the callable it may expect; the previously called decorator 'staticmethod' returns a special object</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>decorated.py</file>
|
||||
<line>40</line>
|
||||
<description>This decorator will not receive a callable it may expect; the built-in decorator returns a special object</description>
|
||||
<line>43</line>
|
||||
<description>This decorator will not receive the callable it may expect; the previously called decorator 'classmethod' returns a special object</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>decorated.py</file>
|
||||
<line>47</line>
|
||||
<description>This decorator will not receive a callable it may expect; the built-in decorator returns a special object</description>
|
||||
<line>50</line>
|
||||
<description>This decorator will not receive the callable it may expect; the previously called decorator 'classmethod' returns a special object</description>
|
||||
</problem>
|
||||
<problem>
|
||||
<file>decorated.py</file>
|
||||
<line>61</line>
|
||||
<description>This decorator will not receive the callable it may expect; the previously called decorator 'classmethod' returns a special object</description>
|
||||
</problem>
|
||||
</problems>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from typing import overload, final
|
||||
|
||||
|
||||
def innocent(f):
|
||||
"A transparent deco"
|
||||
print("I'm innocent!")
|
||||
@@ -49,3 +52,19 @@ class A(object):
|
||||
@innocent
|
||||
def f2(cls):
|
||||
pass
|
||||
|
||||
@overload # nothing
|
||||
@classmethod
|
||||
def g1(cls):
|
||||
pass
|
||||
|
||||
@innocent # warn
|
||||
@overload # nothing
|
||||
@classmethod
|
||||
def g2(cls):
|
||||
pass
|
||||
|
||||
@final # nothing
|
||||
@classmethod
|
||||
def g3(cls):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user