diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java index a34bd7b79656..6add02ed41fb 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java +++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/typing/PyTypingTypeProvider.java @@ -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 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 SYNC_TYPES = List.of(GENERATOR, "typing.Iterable", "typing.Iterator"); - private static final List 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 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; } } diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyReachableElementVisitor.kt b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyReachableElementVisitor.kt new file mode 100644 index 000000000000..ba20569b3d41 --- /dev/null +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyReachableElementVisitor.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java index 82e70bd4645f..7bed62b15d42 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java +++ b/python/python-psi-impl/src/com/jetbrains/python/inspections/PyTypeCheckerInspection.java @@ -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) { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java index ec9f5d403b9e..6320ecc65860 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFunctionImpl.java @@ -340,6 +340,11 @@ public class PyFunctionImpl extends PyBaseElementImpl 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 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()) { diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyYieldExpressionImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyYieldExpressionImpl.java index da230cdb82ef..59918bc2226c 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyYieldExpressionImpl.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyYieldExpressionImpl.java @@ -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(); } diff --git a/python/testData/inspections/PyTypeCheckerInspection/FunctionReturnTypePy3.py b/python/testData/inspections/PyTypeCheckerInspection/FunctionReturnTypePy3.py index ab6ff4ffeea3..4ab6af1ef59a 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/FunctionReturnTypePy3.py +++ b/python/testData/inspections/PyTypeCheckerInspection/FunctionReturnTypePy3.py @@ -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 [x] @@ -54,8 +54,12 @@ def m(x) -> None: def n() -> Generator[int, Any, str]: yield 13 - return 42 + return 42 def o(val) -> int: assert val is int - return val \ No newline at end of file + return val + +def t() -> Iterable[int]: + yield 13 + return "str" # no warning here \ No newline at end of file diff --git a/python/testData/inspections/PyTypeCheckerInspection/FunctionYieldTypePy3.py b/python/testData/inspections/PyTypeCheckerInspection/FunctionYieldTypePy3.py index 625ba1b43ee1..5361dc629897 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/FunctionYieldTypePy3.py +++ b/python/testData/inspections/PyTypeCheckerInspection/FunctionYieldTypePy3.py @@ -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 42 # Suggest AsyncGenerator -async def d() -> Iterable[int]: +async def d() -> Iterable[int]: yield 42 -async def e() -> Iterator[int]: +async def e() -> Iterator[int]: yield 42 async def f() -> Generator[int, str, None]: yield 13 # Suggest sync Generator -def g() -> AsyncIterable[int]: +def g() -> AsyncIterable[int]: yield 42 -def h() -> AsyncIterator[int]: +def h() -> AsyncIterator[int]: yield 42 def i() -> AsyncGenerator[int, str]: @@ -66,4 +66,25 @@ async def r() -> AsyncGenerator[int]: yield 42 def s() -> Generator[int]: - yield from r() \ No newline at end of file + yield from r() + +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 "str" + +class TIterator[T](Protocol): + def __next__(self, /) -> T: + ... + +def y(b: bool) -> TIterator[int]: + if b: + yield 0 + yield "str" diff --git a/python/testData/inspections/PyTypeCheckerInspection/GeneratorTypeHint.py b/python/testData/inspections/PyTypeCheckerInspection/GeneratorTypeHint.py index b40ec61513ab..eb3f98e8b122 100644 --- a/python/testData/inspections/PyTypeCheckerInspection/GeneratorTypeHint.py +++ b/python/testData/inspections/PyTypeCheckerInspection/GeneratorTypeHint.py @@ -1,2 +1,4 @@ +from typing import Generator + def fixture_generator() -> Generator[str, None, None]: yield "Hello World" \ No newline at end of file diff --git a/python/testData/quickFixes/PyMakeFunctionReturnTypeQuickFixTest/makeGenerator.py b/python/testData/quickFixes/PyMakeFunctionReturnTypeQuickFixTest/makeGenerator.py index 2368acb78148..52ad1ed9987c 100644 --- a/python/testData/quickFixes/PyMakeFunctionReturnTypeQuickFixTest/makeGenerator.py +++ b/python/testData/quickFixes/PyMakeFunctionReturnTypeQuickFixTest/makeGenerator.py @@ -1,4 +1,4 @@ -async def gen() -> str: +async def gen() -> str: b: bool = yield "str" if b: b = yield 3.14 \ No newline at end of file diff --git a/python/testData/typing/ignored.txt b/python/testData/typing/ignored.txt index a192d13ede79..b35115b0906e 100644 --- a/python/testData/typing/ignored.txt +++ b/python/testData/typing/ignored.txt @@ -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 diff --git a/python/testSrc/com/jetbrains/python/Py3TypeTest.java b/python/testSrc/com/jetbrains/python/Py3TypeTest.java index 0e3f5bec02ac..31c997b1ae60 100644 --- a/python/testSrc/com/jetbrains/python/Py3TypeTest.java +++ b/python/testSrc/com/jetbrains/python/Py3TypeTest.java @@ -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", """ diff --git a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java index 73eca4d028d2..f007d249dec6 100644 --- a/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java +++ b/python/testSrc/com/jetbrains/python/inspections/Py3TypeCheckerInspectionTest.java @@ -3063,4 +3063,12 @@ def foo(param: str | int) -> TypeGuard[str]: call = empty """); } + + public void testNoWarningIfUnreachable() { + doTestByText(""" + def foo() -> int: + assert False + return "42" # no warning here, because it is unreachable + """); + } }