PY-76874 Conformance test failure: annotations_generators.py

- Refactored PyTypeCheckerInspection.visitPyYieldExpression() to handle delegating and regular yield expressions separately
- Added PyTypeCheckerInspection.visitDelegatingYieldExpression() to validate delegating yield expressions
- Refactored GeneratorTypeDescriptor.create() into .fromGenerator() and .fromGeneratorOrProtocol()
- Inline GeneratorTypeDescriptor.withAsync and GeneratorTypeDescriptor.toPyType

GitOrigin-RevId: e414dc35430290eae9d022f9d8a458966ac90836
This commit is contained in:
Aleksandr.Govenko
2025-08-01 16:38:38 +00:00
committed by intellij-monorepo-bot
parent bc7d99dfa1
commit 19d608d348
12 changed files with 240 additions and 125 deletions
@@ -2071,12 +2071,13 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
if (!function.isGenerator()) {
return wrapInCoroutineType(returnType, function);
}
var desc = GeneratorTypeDescriptor.create(returnType);
var desc = GeneratorTypeDescriptor.fromGenerator(returnType);
if (desc != null) {
return desc.withAsync(true).toPyType(function);
final PyClass classType = PyPsiFacade.getInstance(function.getProject()).createClassByQName(ASYNC_GENERATOR, function);
final List<PyType> generics = Arrays.asList(desc.yieldType, desc.sendType);
return classType != null ? new PyCollectionTypeImpl(classType, false, generics) : null;
}
}
return returnType;
}
@@ -2106,72 +2107,80 @@ public final class PyTypingTypeProvider extends PyTypeProviderWithCustomContext<
}
public record GeneratorTypeDescriptor(
String className,
PyType yieldType, // if YieldType is not specified, it is AnyType
PyType sendType, // if SendType is not specified, it is PyNoneType
PyType returnType // if ReturnType is not specified, it is PyNoneType
@Nullable PyType yieldType,
@Nullable PyType sendType,
@Nullable PyType returnType,
boolean isAsync
) {
private static final List<String> SYNC_TYPES = List.of(GENERATOR, "typing.Iterable", "typing.Iterator");
private static final List<String> ASYNC_TYPES = List.of(ASYNC_GENERATOR, "typing.AsyncIterable", "typing.AsyncIterator");
public static @Nullable GeneratorTypeDescriptor create(@Nullable PyType type) {
final PyClassType classType = as(type, PyClassType.class);
final PyCollectionType genericType = as(type, PyCollectionType.class);
if (classType == null) return null;
/**
* Extracts type parameters from typing.Generator and typing.AsyncGenerator
*/
public static @Nullable GeneratorTypeDescriptor fromGenerator(@Nullable PyType type) {
if (!(type instanceof PyClassType classType)) return null;
final String qName = classType.getClassQName();
if (qName == null) return null;
if (!SYNC_TYPES.contains(qName) && !ASYNC_TYPES.contains(qName)) return null;
boolean isAsync = ASYNC_GENERATOR.equals(qName);
if (!isAsync && !GENERATOR.equals(qName)) return null;
final PyType noneType = PyBuiltinCache.getInstance(classType.getPyClass()).getNoneType();
PyType yieldType = null;
final var noneType = PyBuiltinCache.getInstance(classType.getPyClass()).getNoneType();
PyType sendType = noneType;
PyType returnType = noneType;
if (genericType != null) {
PyType returnType = isAsync ? null : noneType;
if (type instanceof PyCollectionType genericType) {
yieldType = ContainerUtil.getOrElse(genericType.getElementTypes(), 0, yieldType);
if (GENERATOR.equals(qName) || ASYNC_GENERATOR.equals(qName)) {
sendType = ContainerUtil.getOrElse(genericType.getElementTypes(), 1, sendType);
sendType = ContainerUtil.getOrElse(genericType.getElementTypes(), 1, sendType);
returnType = ContainerUtil.getOrElse(genericType.getElementTypes(), 2, returnType);
}
return new GeneratorTypeDescriptor(yieldType, sendType, returnType, isAsync);
}
/**
* Unlike {@link #fromGenerator}, this method can also extract yield type from Protocol types like typing.Iterable
*/
public static @Nullable GeneratorTypeDescriptor fromGeneratorOrProtocol(@Nullable PyType type, @NotNull TypeEvalContext context) {
if (!(type instanceof PyClassType classType)) return null;
GeneratorTypeDescriptor desc = fromGenerator(type);
if (desc != null) {
return desc;
}
if (PyProtocolsKt.isProtocol(classType, context)) {
PyType yieldType;
PyType syncUpcast = PyTypeUtil.convertToType(classType, "typing.Iterable", classType.getPyClass(), context);
if (syncUpcast instanceof PyCollectionType collectionType) {
yieldType = collectionType.getIteratedItemType();
return new GeneratorTypeDescriptor(yieldType, null, null, false);
}
if (GENERATOR.equals(qName)) {
returnType = ContainerUtil.getOrElse(genericType.getElementTypes(), 2, returnType);
PyType asyncUpcast = PyTypeUtil.convertToType(classType, "typing.AsyncIterable", classType.getPyClass(), context);
if (asyncUpcast instanceof PyCollectionType asyncCollectionType) {
yieldType = asyncCollectionType.getIteratedItemType();
return new GeneratorTypeDescriptor(yieldType, null, null, true);
}
// Here we try to understand a yield type by return type of __next__ method of protocol specified in annotation.
// We cannot use convertToType with typing.Iterator here, as it inherits from typing.Iterable
// and requires both __iter__ and __next__, while it should be possible to decide the yield type only by __next__.
// TODO: unify logic with PyTargetExpressionImpl.getIterationType (PY-82453)
PyFunction next = classType.getPyClass().findMethodByName(PyNames.DUNDER_NEXT, true, context);
if (next != null) {
yieldType = context.getReturnType(next);
yieldType = PyTypeChecker.substitute(yieldType, PyTypeChecker.unifyReceiver(classType, context), context);
return new GeneratorTypeDescriptor(yieldType, null, null, false);
}
PyFunction anext = classType.getPyClass().findMethodByName(PyNames.ANEXT, true, context);
if (anext != null) {
yieldType = Ref.deref(unwrapCoroutineReturnType(context.getReturnType(anext)));
yieldType = PyTypeChecker.substitute(yieldType, PyTypeChecker.unifyReceiver(classType, context), context);
return new GeneratorTypeDescriptor(yieldType, null, null, true);
}
}
return new GeneratorTypeDescriptor(qName, yieldType, sendType, returnType);
}
public boolean isAsync() {
return ASYNC_TYPES.contains(className);
}
public GeneratorTypeDescriptor withAsync(boolean async) {
if (async) {
var idx = SYNC_TYPES.indexOf(className);
if (idx == -1) return this;
return new GeneratorTypeDescriptor(ASYNC_TYPES.get(idx), yieldType, sendType, returnType);
}
else {
var idx = ASYNC_TYPES.indexOf(className);
if (idx == -1) return this;
return new GeneratorTypeDescriptor(SYNC_TYPES.get(idx), yieldType, sendType, returnType);
}
}
public @Nullable PyType toPyType(@NotNull PsiElement anchor) {
final PyClass classType = PyPsiFacade.getInstance(anchor.getProject()).createClassByQName(className, anchor);
final List<PyType> generics;
if (GENERATOR.equals(className)) {
generics = Arrays.asList(yieldType, sendType, returnType);
}
else if (ASYNC_GENERATOR.equals(className)) {
generics = Arrays.asList(yieldType, sendType);
}
else {
generics = Collections.singletonList(yieldType);
}
return classType != null ? new PyCollectionTypeImpl(classType, false, generics) : null;
return null;
}
}
@@ -0,0 +1,17 @@
package com.jetbrains.python.inspections
import com.jetbrains.python.codeInsight.controlflow.isUnreachableForInspection
import com.jetbrains.python.psi.PyElement
import com.jetbrains.python.psi.PyElementVisitor
import com.jetbrains.python.psi.types.TypeEvalContext
class PyReachableElementVisitor(
private val delegate: PyElementVisitor,
private val context: TypeEvalContext
) : PyElementVisitor() {
override fun visitPyElement(node: PyElement) {
if (!node.isUnreachableForInspection(context)) {
node.accept(delegate)
}
}
}
@@ -45,7 +45,8 @@ public class PyTypeCheckerInspection extends PyInspection {
if (LOG.isDebugEnabled()) {
session.putUserData(TIME_KEY, System.nanoTime());
}
return new Visitor(holder, PyInspectionVisitor.getContext(session));
TypeEvalContext context = PyInspectionVisitor.getContext(session);
return new PyReachableElementVisitor(new Visitor(holder, context), context);
}
public static class Visitor extends PyInspectionVisitor {
@@ -129,7 +130,7 @@ public class PyTypeCheckerInspection extends PyInspection {
final String expectedName = PythonDocumentationProvider.getVerboseTypeName(expected, myTypeEvalContext);
final String actualName = PythonDocumentationProvider.getTypeName(actual, myTypeEvalContext);
getHolder()
.problem(returnExpr != null ? returnExpr : node, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName))
.problem(returnExpr != null ? returnExpr : node, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName))
.fix(new PyMakeFunctionReturnTypeQuickFix(function, myTypeEvalContext))
.register();
}
@@ -141,74 +142,97 @@ public class PyTypeCheckerInspection extends PyInspection {
public void visitPyYieldExpression(@NotNull PyYieldExpression node) {
ScopeOwner owner = ScopeUtil.getScopeOwner(node);
if (!(owner instanceof PyFunction function)) return;
if (node.isDelegating()) {
visitDelegatingYieldExpression(node, function);
return;
}
final var annotatedGeneratorDesc = getGeneratorDescriptorFromAnnotation(function, node);
if (annotatedGeneratorDesc == null) return;
checkYieldType(annotatedGeneratorDesc.yieldType(), node, function);
}
private void visitDelegatingYieldExpression(@NotNull PyYieldExpression node, @NotNull PyFunction function) {
assert node.isDelegating();
final PyExpression yieldExpr = node.getExpression();
if (yieldExpr != null && node.isDelegating()) {
checkIteratedValue(yieldExpr, false);
}
if (yieldExpr == null) return;
final PyAnnotation annotation = function.getAnnotation();
final String typeCommentAnnotation = function.getTypeCommentAnnotation();
if (annotation == null && typeCommentAnnotation == null) return;
final PyType delegateType = myTypeEvalContext.getType(yieldExpr);
if (delegateType == null) return;
final PyType fullReturnType = myTypeEvalContext.getReturnType(function);
if (fullReturnType == null) return; // fullReturnType is Any
final var generatorDesc = GeneratorTypeDescriptor.create(fullReturnType);
if (generatorDesc == null) {
// expected type is not Iterable, Iterator, Generator or similar
final PyType actual = function.getInferredReturnType(myTypeEvalContext);
String expectedName = PythonDocumentationProvider.getVerboseTypeName(fullReturnType, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(actual, myTypeEvalContext);
getHolder()
.problem(node, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName))
.fix(new PyMakeFunctionReturnTypeQuickFix(function, myTypeEvalContext))
.register();
var delegateDesc = GeneratorTypeDescriptor.fromGeneratorOrProtocol(delegateType, myTypeEvalContext);
if (delegateDesc != null && delegateDesc.isAsync()) {
String delegateName = PythonDocumentationProvider.getTypeName(delegateType, myTypeEvalContext);
registerProblem(yieldExpr, PyPsiBundle.message("INSP.type.checker.yield.from.async.generator", delegateName));
return;
}
final PyType expectedYieldType = generatorDesc.yieldType();
final PyType expectedSendType = generatorDesc.sendType();
if (checkIteratedValue(yieldExpr, false)) return;
final var annotatedGeneratorDesc = getGeneratorDescriptorFromAnnotation(function, node);
if (annotatedGeneratorDesc == null) return;
if (checkYieldType(annotatedGeneratorDesc.yieldType(), node, function)) return;
// Reversed because SendType is contravariant
final PyType expectedSendType = annotatedGeneratorDesc.sendType();
if (delegateDesc != null && !PyTypeChecker.match(delegateDesc.sendType(), expectedSendType, myTypeEvalContext)) {
String expectedName = PythonDocumentationProvider.getVerboseTypeName(expectedSendType, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(delegateDesc.sendType(), myTypeEvalContext);
registerProblem(yieldExpr, PyPsiBundle.message("INSP.type.checker.yield.from.send.type.mismatch", expectedName, actualName));
}
}
private @Nullable GeneratorTypeDescriptor getGeneratorDescriptorFromAnnotation(@NotNull PyFunction function, @NotNull PyYieldExpression yieldExpr) {
final PyAnnotation annotation = function.getAnnotation();
final String typeCommentAnnotation = function.getTypeCommentAnnotation();
if (annotation == null && typeCommentAnnotation == null) return null;
final PyType annotatedReturnType = myTypeEvalContext.getReturnType(function);
if (annotatedReturnType == null) return null;
final var annotatedGeneratorDesc = GeneratorTypeDescriptor.fromGeneratorOrProtocol(annotatedReturnType, myTypeEvalContext);
if (annotatedGeneratorDesc == null) {
final PyType inferredReturnType = function.getInferredReturnType(myTypeEvalContext);
if (!PyTypeChecker.match(annotatedReturnType, inferredReturnType, myTypeEvalContext)) {
String expectedName = PythonDocumentationProvider.getVerboseTypeName(annotatedReturnType, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(inferredReturnType, myTypeEvalContext);
getHolder()
.problem(yieldExpr, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName))
.fix(new PyMakeFunctionReturnTypeQuickFix(function, myTypeEvalContext))
.register();
}
return null;
}
return annotatedGeneratorDesc;
}
private boolean checkYieldType(@Nullable PyType expectedYieldType, @NotNull PyYieldExpression node, @NotNull PyFunction function) {
final PyType thisYieldType = node.getYieldType(myTypeEvalContext);
if (!PyTypeChecker.match(expectedYieldType, thisYieldType, myTypeEvalContext)) {
final PyExpression yieldExpr = node.getExpression();
String expectedName = PythonDocumentationProvider.getVerboseTypeName(expectedYieldType, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(thisYieldType, myTypeEvalContext);
getHolder()
.problem(yieldExpr != null ? yieldExpr : node, PyPsiBundle.message("INSP.type.checker.yield.type.mismatch", expectedName, actualName))
.fix(new PyMakeFunctionReturnTypeQuickFix(function, myTypeEvalContext))
.register();
return true;
}
if (yieldExpr != null && node.isDelegating()) {
final PyType delegateType = myTypeEvalContext.getType(yieldExpr);
var delegateDesc = GeneratorTypeDescriptor.create(delegateType);
if (delegateDesc == null) return;
if (delegateDesc.isAsync()) {
String delegateName = PythonDocumentationProvider.getTypeName(delegateType, myTypeEvalContext);
registerProblem(yieldExpr, PyPsiBundle.message("INSP.type.checker.yield.from.async.generator", delegateName, delegateName));
return;
}
// Reversed because SendType is contravariant
if (!PyTypeChecker.match(delegateDesc.sendType(), expectedSendType, myTypeEvalContext)) {
String expectedName = PythonDocumentationProvider.getVerboseTypeName(expectedSendType, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(delegateDesc.sendType(), myTypeEvalContext);
registerProblem(yieldExpr, PyPsiBundle.message("INSP.type.checker.yield.from.send.type.mismatch", expectedName, actualName));
}
}
return false;
}
public static @Nullable PyType getExpectedReturnStatementType(@NotNull PyFunction function, @NotNull TypeEvalContext typeEvalContext) {
final PyType returnType = typeEvalContext.getReturnType(function);
if (function.isGenerator()) {
final var generatorDesc = GeneratorTypeDescriptor.create(returnType);
final var generatorDesc = GeneratorTypeDescriptor.fromGeneratorOrProtocol(returnType, typeEvalContext);
if (generatorDesc != null) {
return generatorDesc.returnType();
}
return null;
}
if (function.isAsync()) {
return Ref.deref(PyTypingTypeProvider.coroutineOrGeneratorElementType(returnType));
@@ -334,11 +358,12 @@ public class PyTypeCheckerInspection extends PyInspection {
if (hasImplicitReturns) {
final String expectedName = PythonDocumentationProvider.getVerboseTypeName(expected, myTypeEvalContext);
final String actualName = PythonDocumentationProvider.getTypeName(node.getReturnStatementType(myTypeEvalContext), myTypeEvalContext);
final String actualName =
PythonDocumentationProvider.getTypeName(node.getReturnStatementType(myTypeEvalContext), myTypeEvalContext);
final PsiElement annotationValue = annotation != null ? annotation.getValue() : node.getTypeComment();
if (annotationValue != null) {
getHolder()
.problem(annotationValue, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName))
.problem(annotationValue, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName))
.fix(new PyMakeFunctionReturnTypeQuickFix(node, myTypeEvalContext))
.register();
}
@@ -353,10 +378,12 @@ public class PyTypeCheckerInspection extends PyInspection {
}
if (node.isGenerator()) {
boolean shouldBeAsync = node.isAsync() && node.isAsyncAllowed();
final var generatorDesc = GeneratorTypeDescriptor.create(annotatedType);
if (generatorDesc != null && generatorDesc.isAsync() != shouldBeAsync) {
final PyType inferredType = node.getInferredReturnType(myTypeEvalContext);
final var generatorDesc = GeneratorTypeDescriptor.fromGeneratorOrProtocol(annotatedType, myTypeEvalContext);
final boolean shouldBeAsync = node.isAsync() && node.isAsyncAllowed();
final boolean wrongSyncAsync = generatorDesc != null && generatorDesc.isAsync() != shouldBeAsync;
final PyType inferredType = node.getInferredReturnType(myTypeEvalContext);
if (wrongSyncAsync || (generatorDesc == null && !PyTypeChecker.match(annotatedType, inferredType, myTypeEvalContext))) {
String expectedName = PythonDocumentationProvider.getVerboseTypeName(inferredType, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(annotatedType, myTypeEvalContext);
final PsiElement annotationValue = annotation != null ? annotation.getValue() : node.getTypeComment();
@@ -394,7 +421,7 @@ public class PyTypeCheckerInspection extends PyInspection {
}
}
private void checkIteratedValue(@Nullable PyExpression iteratedValue, boolean isAsync) {
private boolean checkIteratedValue(@Nullable PyExpression iteratedValue, boolean isAsync) {
if (iteratedValue != null) {
final PyType type = myTypeEvalContext.getType(iteratedValue);
final String iterableClassName = isAsync ? PyNames.ASYNC_ITERABLE : PyNames.ITERABLE;
@@ -406,8 +433,10 @@ public class PyTypeCheckerInspection extends PyInspection {
String qualifiedName = "collections." + iterableClassName;
registerProblem(iteratedValue, PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", qualifiedName, typeName));
return true;
}
}
return false;
}
private void checkContextManagerValue(@Nullable PyExpression iteratedValue, boolean isAsync) {
@@ -340,6 +340,11 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
public void visitPyLambdaExpression(@NotNull PyLambdaExpression node) {
// Ignore nested lambdas
}
@Override
public void visitPyClass(@NotNull PyClass node) {
// Ignore nested classes
}
}
/**
@@ -646,6 +651,16 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
// Ignore nested functions
}
@Override
public void visitPyLambdaExpression(@NotNull PyLambdaExpression node) {
// Ignore lambdas
}
@Override
public void visitPyClass(@NotNull PyClass node) {
// Ignore nested classes
}
@Override
public void visitElement(@NotNull PsiElement element) {
if (!containsYield.get()) {
@@ -8,7 +8,8 @@ import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyYieldExpression;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -28,7 +29,7 @@ public class PyYieldExpressionImpl extends PyElementImpl implements PyYieldExpre
if (isDelegating()) {
final PyExpression e = getExpression();
final PyType type = e != null ? context.getType(e) : null;
var generatorDesc = PyTypingTypeProvider.GeneratorTypeDescriptor.create(type);
var generatorDesc = PyTypingTypeProvider.GeneratorTypeDescriptor.fromGeneratorOrProtocol(type, context);
if (generatorDesc != null) {
return generatorDesc.returnType();
}
@@ -55,7 +56,7 @@ public class PyYieldExpressionImpl extends PyElementImpl implements PyYieldExpre
if (ScopeUtil.getScopeOwner(this) instanceof PyFunction function) {
if (function.getAnnotation() != null || function.getTypeCommentAnnotation() != null) {
var returnType = context.getReturnType(function);
var generatorDesc = PyTypingTypeProvider.GeneratorTypeDescriptor.create(returnType);
var generatorDesc = PyTypingTypeProvider.GeneratorTypeDescriptor.fromGeneratorOrProtocol(returnType, context);
if (generatorDesc != null) {
return generatorDesc.sendType();
}
@@ -65,7 +66,7 @@ public class PyYieldExpressionImpl extends PyElementImpl implements PyYieldExpre
if (isDelegating()) {
final PyExpression e = getExpression();
final PyType type = e != null ? context.getType(e) : null;
var generatorDesc = PyTypingTypeProvider.GeneratorTypeDescriptor.create(type);
var generatorDesc = PyTypingTypeProvider.GeneratorTypeDescriptor.fromGeneratorOrProtocol(type, context);
if (generatorDesc != null) {
return generatorDesc.sendType();
}
@@ -1,4 +1,4 @@
from typing import List, Optional, Union
from typing import List, Optional, Union, Generator, Iterable
def a(x: List[int]) -> List[str]:
return <warning descr="Expected type 'list[str]', got 'list[list[int]]' instead">[x]</warning>
@@ -54,8 +54,12 @@ def m(x) -> None:
def n() -> Generator[int, Any, str]:
yield 13
return 42
return <warning descr="Expected type 'str', got 'int' instead">42</warning>
def o(val) -> int:
assert val is int
return val
return val
def t() -> Iterable[int]:
yield 13
return "str" # no warning here
@@ -1,4 +1,4 @@
from typing import Generator, Iterable, Iterator, AsyncIterable, AsyncIterator, AsyncGenerator
from typing import Generator, Iterable, Iterator, AsyncIterable, AsyncIterator, AsyncGenerator, Protocol
# Fix incorrect YieldType
def a() -> Iterable[str]:
@@ -16,20 +16,20 @@ def c() -> Generator[int, Any, str]:
return <warning descr="Expected type 'str', got 'int' instead">42</warning>
# Suggest AsyncGenerator
async def d() -> <warning descr="Expected type 'AsyncGenerator[int, None]', got 'Iterable[int]' instead">Iterable[int]</warning>:
async def d() -> <warning descr="Expected type 'AsyncGenerator[int, Any]', got 'Iterable[int]' instead">Iterable[int]</warning>:
yield 42
async def e() -> <warning descr="Expected type 'AsyncGenerator[int, None]', got 'Iterator[int]' instead">Iterator[int]</warning>:
async def e() -> <warning descr="Expected type 'AsyncGenerator[int, Any]', got 'Iterator[int]' instead">Iterator[int]</warning>:
yield 42
async def f() -> <warning descr="Expected type 'AsyncGenerator[int, str]', got 'Generator[int, str, None]' instead">Generator[int, str, None]</warning>:
yield 13
# Suggest sync Generator
def g() -> <warning descr="Expected type 'Generator[int, None, None]', got 'AsyncIterable[int]' instead">AsyncIterable[int]</warning>:
def g() -> <warning descr="Expected type 'Generator[int, Any, None]', got 'AsyncIterable[int]' instead">AsyncIterable[int]</warning>:
yield 42
def h() -> <warning descr="Expected type 'Generator[int, None, None]', got 'AsyncIterator[int]' instead">AsyncIterator[int]</warning>:
def h() -> <warning descr="Expected type 'Generator[int, Any, None]', got 'AsyncIterator[int]' instead">AsyncIterator[int]</warning>:
yield 42
def i() -> <warning descr="Expected type 'Generator[int, str, None]', got 'AsyncGenerator[int, str]' instead">AsyncGenerator[int, str]</warning>:
@@ -66,4 +66,25 @@ async def r() -> AsyncGenerator[int]:
yield 42
def s() -> Generator[int]:
yield from <warning descr="Cannot yield from 'AsyncGenerator[int, None]', use async for instead"><warning descr="Expected type 'collections.Iterable', got 'AsyncGenerator[int, None]' instead">r()</warning></warning>
yield from <warning descr="Cannot yield from 'AsyncGenerator[int, None]', use async for instead">r()</warning>
def t() -> object: # no error here
yield None # no error here
class IntIterator(Protocol):
def __next__(self, /) -> int:
...
def x(b: bool) -> IntIterator:
if b:
yield 0
yield <warning descr="Expected yield type 'int', got 'str' instead">"str"</warning>
class TIterator[T](Protocol):
def __next__(self, /) -> T:
...
def y(b: bool) -> TIterator[int]:
if b:
yield 0
yield <warning descr="Expected yield type 'int', got 'str' instead">"str"</warning>
@@ -1,2 +1,4 @@
from typing import Generator
def fixture_generator() -> Generator[str, None, None]:
yield "Hello World"
@@ -1,4 +1,4 @@
async def gen() -> str:
async def gen() -> <warning descr="Expected type 'AsyncGenerator[str | float, Any]', got 'str' instead">str</warning>:
b: bool = <warning descr="Expected type 'str', got 'AsyncGenerator[str | float, Any]' instead"><caret>yield "str"</warning>
if b:
b = <warning descr="Expected type 'str', got 'AsyncGenerator[str | float, Any]' instead">yield 3.14</warning>
-1
View File
@@ -2,7 +2,6 @@ aliases_implicit.py
aliases_recursive.py
aliases_typealiastype.py
aliases_variance.py
annotations_generators.py
annotations_typeexpr.py
callables_annotation.py
callables_kwargs.py
@@ -17,6 +17,16 @@ import java.util.Map;
public class Py3TypeTest extends PyTestCase {
public static final String TEST_DIRECTORY = "/types/";
public void testYieldInsideLambda() {
// Checks that foo is not a generator
doTest("int", """
def foo():
y = lambda x: (yield x)
return 42
expr = foo()
""");
}
// PY-21069
public void testDunderGetattr() {
doTest("MyClass", """
@@ -3063,4 +3063,12 @@ def foo(param: str | int) -> TypeGuard[str]:
call = <warning descr="Expected type '(Concatenate(int, ...)) -> str', got '() -> str' instead">empty</warning>
""");
}
public void testNoWarningIfUnreachable() {
doTestByText("""
def foo() -> int:
assert False
return "42" # no warning here, because it is unreachable
""");
}
}