diff --git a/python/psi-api/src/com/jetbrains/python/PyNames.java b/python/psi-api/src/com/jetbrains/python/PyNames.java index bc89d601cb4d..d8b39a07605f 100644 --- a/python/psi-api/src/com/jetbrains/python/PyNames.java +++ b/python/psi-api/src/com/jetbrains/python/PyNames.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * 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. @@ -170,6 +170,7 @@ public class PyNames { public static final String ANEXT = "__anext__"; public static final String AENTER = "__aenter__"; public static final String AEXIT = "__aexit__"; + public static final String DUNDER_AWAIT = "__await__"; public static final String SIZEOF = "__sizeof__"; public static final String INIT_SUBCLASS = "__init_subclass__"; public static final String FSPATH = "__fspath__"; @@ -426,7 +427,7 @@ public class PyNames { .put("__imatmul__", _self_other_descr) .put("__matmul__", _self_other_descr) .put("__rmatmul__", _self_other_descr) - .put("__await__", _only_self_descr) + .put(DUNDER_AWAIT, _only_self_descr) .put(AENTER, _only_self_descr) .put(AEXIT, _exit_descr) .put(AITER, _only_self_descr) diff --git a/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java b/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java index b755f3114455..33272bc5c10d 100644 --- a/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyDeprecationInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * 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. @@ -89,7 +89,9 @@ public class PyDeprecationInspection extends PyInspection { if (LanguageLevel.forElement(node).isAtLeast(LanguageLevel.PYTHON33) && decoratorList != null) { Arrays .stream(decoratorList.getDecorators()) - .filter(decorator -> PyKnownDecoratorUtil.asKnownDecorator(decorator, myTypeEvalContext) == KnownDecorator.ABC_ABSTRACTPROPERTY) + .filter( + decorator -> PyKnownDecoratorUtil.asKnownDecorators(decorator, myTypeEvalContext).contains(KnownDecorator.ABC_ABSTRACTPROPERTY) + ) .forEach( decorator -> { final QualifiedName abcAbsPropertyQName = KnownDecorator.ABC_ABSTRACTPROPERTY.getQualifiedName(); diff --git a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java index e5e6b0293243..0c136a2a450e 100644 --- a/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java +++ b/python/src/com/jetbrains/python/inspections/unresolvedReference/PyUnresolvedReferencesInspection.java @@ -47,6 +47,7 @@ import com.jetbrains.python.codeInsight.imports.AutoImportHintAction; import com.jetbrains.python.codeInsight.imports.AutoImportQuickFix; import com.jetbrains.python.codeInsight.imports.OptimizeImportsQuickFix; import com.jetbrains.python.codeInsight.imports.PythonImportUtils; +import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider; import com.jetbrains.python.console.PydevConsoleRunner; import com.jetbrains.python.documentation.docstrings.DocStringParameterReference; import com.jetbrains.python.documentation.docstrings.DocStringTypeReference; @@ -68,6 +69,7 @@ import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.types.*; import com.jetbrains.python.sdk.PythonSdkType; import com.jetbrains.python.sdk.skeletons.PySkeletonRefresher; +import one.util.streamex.StreamEx; import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -789,7 +791,7 @@ public class PyUnresolvedReferencesInspection extends PyInspection { } } } - if (type instanceof PyClassTypeImpl) { + if (type instanceof PyClassType) { PyClass cls = ((PyClassType)type).getPyClass(); if (PyTypeChecker.overridesGetAttr(cls, myTypeEvalContext)) { return true; @@ -804,6 +806,8 @@ public class PyUnresolvedReferencesInspection extends PyInspection { return true; } if (hasUnresolvedDynamicMember((PyClassType)type, reference, name, myTypeEvalContext)) return true; + + if (isAwaitOnGeneratorBasedCoroutine(name, reference, cls)) return true; } if (type instanceof PyFunctionTypeImpl) { final PyCallable callable = ((PyFunctionTypeImpl)type).getCallable(); @@ -854,6 +858,25 @@ public class PyUnresolvedReferencesInspection extends PyInspection { return false; } + private boolean isAwaitOnGeneratorBasedCoroutine(@NotNull String name, @NotNull PsiReference reference, @NotNull PyClass cls) { + if (PyNames.DUNDER_AWAIT.equals(name) && + reference instanceof PyOperatorReference && + PyTypingTypeProvider.GENERATOR.equals(cls.getQualifiedName())) { + final PyExpression receiver = ((PyOperatorReference)reference).getReceiver(); + + if (receiver instanceof PyCallExpression) { + final boolean resolvedToGeneratorBasedCoroutine = StreamEx + .of(((PyCallExpression)receiver).multiResolveCalleeFunction(getResolveContext())) + .select(PyFunction.class) + .anyMatch(function -> PyKnownDecoratorUtil.hasGeneratorBasedCoroutineDecorator(function, myTypeEvalContext)); + + if (resolvedToGeneratorBasedCoroutine) return true; + } + } + + return false; + } + private void addCreateMemberFromUsageFixes(PyType type, PsiReference reference, String refText, List actions) { PsiElement element = reference.getElement(); if (type instanceof PyClassTypeImpl) { diff --git a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java index 92039a77ec2b..096a4c435a39 100644 --- a/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java +++ b/python/src/com/jetbrains/python/psi/PyKnownDecoratorUtil.java @@ -15,11 +15,10 @@ */ package com.jetbrains.python.psi; -import com.google.common.collect.Iterators; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; import com.intellij.psi.util.QualifiedName; -import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.types.TypeEvalContext; import one.util.streamex.StreamEx; @@ -28,7 +27,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -import static com.intellij.util.containers.ContainerUtil.newMapFromValues; import static com.jetbrains.python.psi.PyKnownDecoratorUtil.KnownDecorator.*; import static com.jetbrains.python.psi.PyUtil.as; @@ -66,7 +64,9 @@ public class PyKnownDecoratorUtil { //ATEXIT_REGISTER("atexit.register", true), //ATEXIT_UNREGISTER("atexit.unregister", false), - ASYNCIO_COROUTINE("asyncio.tasks.coroutine"), + ASYNCIO_TASKS_COROUTINE("asyncio.tasks.coroutine"), + ASYNCIO_COROUTINES_COROUTINE("asyncio.coroutines.coroutine"), + TYPES_COROUTINE("types.coroutine"), UNITTEST_SKIP("unittest.case.skip"), UNITTEST_SKIP_IF("unittest.case.skipIf"), @@ -112,13 +112,11 @@ public class PyKnownDecoratorUtil { DJANGO_UTILS_FUNCTIONAL_CACHED_PROPERTY, KOMBU_UTILS_CACHED_PROPERTY); - private static final Map ourByShortName = newMapFromValues(Iterators.forArray(values()), - new Convertor() { - @Override - public String convert(KnownDecorator o) { - return o.getShortName(); - } - }); + private static final Set GENERATOR_BASED_COROUTINE_DECORATORS = EnumSet.of(ASYNCIO_TASKS_COROUTINE, + ASYNCIO_COROUTINES_COROUTINE, + TYPES_COROUTINE); + + private static final Map> BY_SHORT_NAME = StreamEx.of(values()).groupingBy(KnownDecorator::getShortName); /** * Map decorators of element to {@link PyKnownDecoratorUtil.KnownDecorator}. @@ -137,16 +135,16 @@ public class PyKnownDecoratorUtil { return StreamEx .of(decoratorList.getDecorators()) - .map(decorator -> asKnownDecorator(decorator, context)) + .flatMap(decorator -> asKnownDecorators(decorator, context).stream()) .nonNull() .toList(); } - @Nullable - public static KnownDecorator asKnownDecorator(@NotNull PyDecorator decorator, @NotNull TypeEvalContext context) { + @NotNull + public static List asKnownDecorators(@NotNull PyDecorator decorator, @NotNull TypeEvalContext context) { final QualifiedName qualifiedName = decorator.getQualifiedName(); if (qualifiedName == null) { - return null; + return Collections.emptyList(); } if (context.maySwitchToAST(decorator)) { @@ -157,18 +155,16 @@ public class PyKnownDecoratorUtil { if (resolved != null && resolved.getQualifiedName() != null) { final QualifiedName resolvedName = QualifiedName.fromDottedString(resolved.getQualifiedName()); - final KnownDecorator knownDecorator = ourByShortName.get(resolvedName.getLastComponent()); + final List knownDecorators = BY_SHORT_NAME.getOrDefault(resolvedName.getLastComponent(), Collections.emptyList()); - if (knownDecorator != null && resolvedName.equals(knownDecorator.getQualifiedName())) { - return knownDecorator; - } + return ContainerUtil.filter(knownDecorators, knownDecorator -> resolvedName.equals(knownDecorator.getQualifiedName())); } } else { - return ourByShortName.get(qualifiedName.getLastComponent()); + return BY_SHORT_NAME.getOrDefault(qualifiedName.getLastComponent(), Collections.emptyList()); } - return null; + return Collections.emptyList(); } @Nullable @@ -231,7 +227,11 @@ public class PyKnownDecoratorUtil { } public static boolean isPropertyDecorator(@NotNull PyDecorator decorator, @NotNull TypeEvalContext context) { - return PROPERTY_DECORATORS.contains(asKnownDecorator(decorator, context)); + return ContainerUtil.exists(asKnownDecorators(decorator, context), PROPERTY_DECORATORS::contains); + } + + public static boolean hasGeneratorBasedCoroutineDecorator(@NotNull PyFunction function, @NotNull TypeEvalContext context) { + return ContainerUtil.exists(getKnownDecorators(function, context), GENERATOR_BASED_COROUTINE_DECORATORS::contains); } private static boolean allDecoratorsAreKnown(@NotNull PyDecoratable element, @NotNull List decorators) { diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/a.py new file mode 100644 index 000000000000..943c7e59241c --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/a.py @@ -0,0 +1,10 @@ +import asyncio + + +@asyncio.coroutine +def foo(): + yield from asyncio.sleep(1) + return 3 + +async def bar(): + return await foo() * 2 \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/__init__.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/__init__.py new file mode 100644 index 000000000000..4cbdad176cad --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/__init__.py @@ -0,0 +1,5 @@ +from .coroutines import * + + +def sleep(i): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/coroutines.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/coroutines.py new file mode 100644 index 000000000000..68f0e4fbb99a --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/coroutines.py @@ -0,0 +1,2 @@ +def coroutine(fn): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/a.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/a.py new file mode 100644 index 000000000000..6ac4117f9808 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/a.py @@ -0,0 +1,11 @@ +import types +import asyncio + + +@types.coroutine +def foo(): + yield from asyncio.sleep(1) + return 3 + +async def bar(): + return await foo() * 2 \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/asyncio.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/asyncio.py new file mode 100644 index 000000000000..fad29386087d --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/asyncio.py @@ -0,0 +1,2 @@ +def sleep(i): + pass \ No newline at end of file diff --git a/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/types.py b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/types.py new file mode 100644 index 000000000000..e12a53cfcdc3 --- /dev/null +++ b/python/testData/inspections/PyUnresolvedReferencesInspection3K/UsageOfFunctionDecoratedWithTypesCoroutine/types.py @@ -0,0 +1,6 @@ +def coroutine(fn): + pass + + +def sleep(i): + pass \ No newline at end of file diff --git a/python/testData/types/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/__init__.py b/python/testData/types/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/__init__.py new file mode 100644 index 000000000000..4cbdad176cad --- /dev/null +++ b/python/testData/types/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/__init__.py @@ -0,0 +1,5 @@ +from .coroutines import * + + +def sleep(i): + pass \ No newline at end of file diff --git a/python/testData/types/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/coroutines.py b/python/testData/types/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/coroutines.py new file mode 100644 index 000000000000..68f0e4fbb99a --- /dev/null +++ b/python/testData/types/UsageOfFunctionDecoratedWithAsyncioCoroutine/asyncio/coroutines.py @@ -0,0 +1,2 @@ +def coroutine(fn): + pass \ No newline at end of file diff --git a/python/testData/types/UsageOfFunctionDecoratedWithTypesCoroutine/asyncio.py b/python/testData/types/UsageOfFunctionDecoratedWithTypesCoroutine/asyncio.py new file mode 100644 index 000000000000..fad29386087d --- /dev/null +++ b/python/testData/types/UsageOfFunctionDecoratedWithTypesCoroutine/asyncio.py @@ -0,0 +1,2 @@ +def sleep(i): + pass \ No newline at end of file diff --git a/python/testData/types/UsageOfFunctionDecoratedWithTypesCoroutine/types.py b/python/testData/types/UsageOfFunctionDecoratedWithTypesCoroutine/types.py new file mode 100644 index 000000000000..e12a53cfcdc3 --- /dev/null +++ b/python/testData/types/UsageOfFunctionDecoratedWithTypesCoroutine/types.py @@ -0,0 +1,6 @@ +def coroutine(fn): + pass + + +def sleep(i): + pass \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index b7054eac2008..82a490c24c8f 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -519,6 +519,35 @@ public class Py3TypeTest extends PyTestCase { " print(expr)"); } + // PY-21655 + public void testUsageOfFunctionDecoratedWithAsyncioCoroutine() { + myFixture.copyDirectoryToProject(TEST_DIRECTORY + getTestName(false), ""); + runWithLanguageLevel(LanguageLevel.PYTHON35, () -> doTest("int", + "import asyncio\n" + + "@asyncio.coroutine\n" + + "def foo():\n" + + " yield from asyncio.sleep(1)\n" + + " return 3\n" + + "async def bar():\n" + + " expr = await foo()\n" + + " return expr")); + } + + // PY-21655 + public void testUsageOfFunctionDecoratedWithTypesCoroutine() { + myFixture.copyDirectoryToProject(TEST_DIRECTORY + getTestName(false), ""); + runWithLanguageLevel(LanguageLevel.PYTHON35, () -> doTest("int", + "import asyncio\n" + + "import types\n" + + "@types.coroutine\n" + + "def foo():\n" + + " yield from asyncio.sleep(1)\n" + + " return 3\n" + + "async def bar():\n" + + " expr = await foo()\n" + + " return expr")); + } + // PY-22513 public void testGenericKwargs() { doTest("Dict[str, Union[int, str]]", diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java index 5862048b9968..eeece07ead9e 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3UnresolvedReferencesInspectionTest.java @@ -206,6 +206,16 @@ public class Py3UnresolvedReferencesInspectionTest extends PyTestCase { doTest(); } + // PY-21655 + public void testUsageOfFunctionDecoratedWithAsyncioCoroutine() { + doMultiFileTest("a.py"); + } + + // PY-21655 + public void testUsageOfFunctionDecoratedWithTypesCoroutine() { + doMultiFileTest("a.py"); + } + // PY-22899, PY-22937 public void testCallTypeGetAttributeAndSetAttrInInheritor() { doTest();