PY-20657 Loose return types of generators annotated to return not typing.Generator

Sometimes people use types other than typing.Generator to annotate result
types of generator functions, e.g. Iterable[T]. Because these other generic
types don't allow to specify type of expressions in return statements,
consider it to be Any, so as not to warn about mismatched actual and expected
return types in type checker inspection.
This commit is contained in:
Mikhail Golubev
2016-12-13 14:03:33 +03:00
parent 11c0f0acdd
commit 2921c0ef9e
5 changed files with 50 additions and 4 deletions
@@ -72,6 +72,11 @@ public interface PyFunction extends PsiNamedElement, StubBasedPsiElement<PyFunct
@Nullable
Modifier getModifier();
/**
* Checks whether the function contains a yield expression in its body.
*/
boolean isGenerator();
boolean isAsync();
boolean isAsyncAllowed();
@@ -115,10 +115,16 @@ public class PyTypeCheckerInspection extends PyInspection {
if (PyNames.FAKE_COROUTINE.equals(genericType.getName())) {
return genericType.getIteratedItemType();
}
else if (PyNames.FAKE_GENERATOR.equals(genericType.getName()) ||
genericType instanceof PyClassType && "typing.Generator".equals(((PyClassType)genericType).getClassQName())) {
// Generator's type is parametrized as [YieldType, SendType, ReturnType]
return ContainerUtil.getOrElse(genericType.getElementTypes(myTypeEvalContext), 2, null);
else if (function.isGenerator()) {
if (PyNames.FAKE_GENERATOR.equals(genericType.getName()) ||
genericType instanceof PyClassType && "typing.Generator".equals(((PyClassType)genericType).getClassQName())) {
// Generator's type is parametrized as [YieldType, SendType, ReturnType]
return ContainerUtil.getOrElse(genericType.getElementTypes(myTypeEvalContext), 2, null);
}
else {
// Assume that any other return type annotation for a generator cannot contain its return type
return null;
}
}
}
@@ -693,6 +693,30 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
return null;
}
@Override
public boolean isGenerator() {
final Ref<Boolean> result = new Ref<>(false);
getStatementList().accept(new PyRecursiveElementVisitor() {
@Override
public void visitPyYieldExpression(PyYieldExpression node) {
result.set(true);
}
@Override
public void visitPyFunction(PyFunction node) {
// Ignore nested functions
}
@Override
public void visitElement(PsiElement element) {
if (!result.get()) {
super.visitElement(element);
}
}
});
return result.get();
}
@Override
public boolean isAsync() {
final PyFunctionStub stub = getStub();
@@ -0,0 +1,6 @@
from typing import Iterable
def f() -> Iterable[int]:
for i in range(10):
yield i
@@ -179,6 +179,11 @@ public class Py3TypeCheckerInspectionTest extends PyTestCase {
doTest();
}
// PY-20657
public void testGeneratorAnnotatedToReturnIterable() {
doTest();
}
// PY-21083
public void testFloatFromhex() {
doTest();