diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index eade6b1190ee..7c510a075f64 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -826,7 +826,8 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } if (type instanceof PyFunctionTypeImpl) { final PyCallable callable = ((PyFunctionTypeImpl)type).getCallable(); - if (callable instanceof PyFunction && PyKnownDecoratorUtil.hasUnknownDecorator((PyFunction)callable, myTypeEvalContext)) { + if (callable instanceof PyFunction && + PyKnownDecoratorUtil.hasUnknownOrUpdatingAttributesDecorator((PyFunction)callable, myTypeEvalContext)) { return true; } } diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index 9ab09cdaf5ae..1fc524d4595f 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -219,6 +219,21 @@ public class PyKnownDecoratorUtil { return !allDecoratorsAreKnown(decoratable, decorators) || decorators.contains(UNITTEST_MOCK_PATCH); } + public static boolean hasUnknownOrUpdatingAttributesDecorator(@NotNull PyDecoratable decoratable, @NotNull TypeEvalContext context) { + final List decorators = getKnownDecorators(decoratable, context); + + if (!allDecoratorsAreKnown(decoratable, decorators)) { + return true; + } + + return ContainerUtil.exists( + decorators, + d -> + d == FUNCTOOLS_LRU_CACHE || // cache_clear, cache_info + d == FUNCTOOLS_SINGLEDISPATCH // dispatch, register, registry + ); + } + private static boolean allDecoratorsAreKnown(@NotNull PyDecoratable element, @NotNull List decorators) { final PyDecoratorList decoratorList = element.getDecoratorList(); return decoratorList == null ? decorators.isEmpty() : decoratorList.getDecorators().length == decorators.size(); diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/usingFunctoolsSingledispatch.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/usingFunctoolsSingledispatch.py new file mode 100644 index 000000000000..e02eadae6347 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/usingFunctoolsSingledispatch.py @@ -0,0 +1,16 @@ +from functools import singledispatch + + +@singledispatch +def to_description(ob): + return str(ob) + + +@to_description.register(type(None)) +def none_to_description(_): + return '–' + + +@to_description.register(bool) +def bool_to_description(b): + return '✓' if b else '' diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 62856bc3cd59..3f6ebe5af9d6 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -265,4 +265,9 @@ public class Py3UnresolvedReferencesInspectionTest extends PyInspectionTestCase public void testNamespacePackageRedundantUnion() { doMultiFileTest("a.py", Arrays.asList("root1", "root2")); } + + // PY-27964 + public void testUsingFunctoolsSingledispatch() { + doTest(); + } }