PY-21655 Fixed: False inspection: @asyncio.coroutine decorated function treated as non-awaitable

Don't mark "await" on generator based coroutines as unresolved.
This commit is contained in:
Semyon Proshev
2017-04-19 17:12:26 +03:00
parent af1c31f4ae
commit cf31442658
16 changed files with 143 additions and 27 deletions
@@ -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)
@@ -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();
@@ -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<LocalQuickFix> actions) {
PsiElement element = reference.getElement();
if (type instanceof PyClassTypeImpl) {
@@ -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<String, KnownDecorator> ourByShortName = newMapFromValues(Iterators.forArray(values()),
new Convertor<KnownDecorator, String>() {
@Override
public String convert(KnownDecorator o) {
return o.getShortName();
}
});
private static final Set<KnownDecorator> GENERATOR_BASED_COROUTINE_DECORATORS = EnumSet.of(ASYNCIO_TASKS_COROUTINE,
ASYNCIO_COROUTINES_COROUTINE,
TYPES_COROUTINE);
private static final Map<String, List<KnownDecorator>> 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<KnownDecorator> 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<KnownDecorator> 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<KnownDecorator> decorators) {
@@ -0,0 +1,10 @@
import asyncio
@asyncio.coroutine
def foo():
yield from asyncio.sleep(1)
return 3
async def bar():
return await foo() * 2
@@ -0,0 +1,5 @@
from .coroutines import *
def sleep(i):
pass
@@ -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
@@ -0,0 +1,6 @@
def coroutine(fn):
pass
def sleep(i):
pass
@@ -0,0 +1,5 @@
from .coroutines import *
def sleep(i):
pass
@@ -0,0 +1,2 @@
def coroutine(fn):
pass
@@ -0,0 +1,2 @@
def sleep(i):
pass
@@ -0,0 +1,6 @@
def coroutine(fn):
pass
def sleep(i):
pass
@@ -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]]",
@@ -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();