mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
committed by
intellij-monorepo-bot
parent
bc7d99dfa1
commit
19d608d348
+67
-58
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+81
-52
@@ -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>
|
||||
@@ -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
|
||||
""");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user