PY-20611 Missing warning about functions implicitly returning None when return type is not Optional

Updated PyFunction to account for implicit 'return None' statements when inferring return statement types.

It affected return type inference of PyFunction.

Fixed a failing test related to formatted strings.

Added a quick fix to make all return statements explicit.

Updated the CFG to include PyPassStatements, enabling detection of exit points in empty functions.

Simplified PyMakeFunctionReturnTypeQuickFix to independently infer function types and handle required imports. Currently, it does not support specifying custom suggested types.



Merge-request: IJ-MR-148719
Merged-by: Aleksandr Govenko <aleksandr.govenko@jetbrains.com>

(cherry picked from commit 9f58961f9eb70e4f9dbba7359f5aafdfd392b7e2)

IJ-MR-148719

GitOrigin-RevId: 68ef5c4a1cc0fcaffd750cc0713250a106136643
This commit is contained in:
Aleksandr.Govenko
2024-11-26 17:02:37 +00:00
committed by intellij-monorepo-bot
parent 137b1d2b13
commit 4dd41ee9f5
32 changed files with 430 additions and 301 deletions

View File

@@ -460,6 +460,9 @@ QFIX.remove.decorator=Remove decorator
QFIX.NAME.make.function.return.type=Make function return inferred type
QFIX.make.function.return.type=Make ''{0}'' return ''{1}''
# PyMakeReturnsExplicitQuickFix
QFIX.NAME.make.return.stmts.explicit=Make 'return None' statements explicit
# Add method quick-fix
QFIX.NAME.add.method.to.class=Add method to class
QFIX.add.method.to.class=Add method {0}() to class {1}
@@ -1055,7 +1058,7 @@ INSP.NAME.type.checker=Incorrect type
INSP.type.checker.expected.type.got.type.instead=Expected type ''{0}'', got ''{1}'' instead
INSP.type.checker.typed.dict.extra.key=Extra key ''{0}'' for TypedDict ''{1}''
INSP.type.checker.typed.dict.missing.keys=TypedDict ''{0}'' has missing {1,choice,1#key|2#keys}: {2}
INSP.type.checker.expected.to.return.type.got.no.return=Expected to return ''{0}'', got no return
INSP.type.checker.returning.type.has.implicit.return=Function returning ''{0}'' has implicit ''return None''
INSP.type.checker.init.should.return.none=__init__ should return None
INSP.type.checker.type.does.not.have.expected.attribute=Type ''{0}'' doesn''t have expected {1,choice,1#attribute|2#attributes} {2}
INSP.type.checker.only.concrete.class.can.be.used.where.matched.protocol.expected=Only a concrete class can be used where ''{0}'' (matched generic type ''{1}'') protocol is expected

View File

@@ -628,7 +628,7 @@ public final class PyStringFormatInspection extends PyInspection {
allForSure = allForSure && elementsCount != -1;
maxNumber = Math.max(maxNumber, elementsCount);
}
else {
else if (!(member instanceof PyNoneType)) {
allForSure = false;
}
}

View File

@@ -18,6 +18,7 @@ import com.jetbrains.python.codeInsight.typing.PyProtocolsKt;
import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider;
import com.jetbrains.python.documentation.PythonDocumentationProvider;
import com.jetbrains.python.inspections.quickfix.PyMakeFunctionReturnTypeQuickFix;
import com.jetbrains.python.inspections.quickfix.PyMakeReturnsExplicitFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.types.*;
@@ -27,6 +28,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.util.containers.ContainerUtil.exists;
import static com.jetbrains.python.psi.PyUtil.as;
import static com.jetbrains.python.psi.impl.PyCallExpressionHelper.*;
@@ -44,10 +46,17 @@ public class PyTypeCheckerInspection extends PyInspection {
}
public static class Visitor extends PyInspectionVisitor {
public Visitor(@Nullable ProblemsHolder holder, @NotNull TypeEvalContext context) {
public Visitor(@NotNull ProblemsHolder holder, @NotNull TypeEvalContext context) {
super(holder, context);
}
@Override
protected @NotNull ProblemsHolder getHolder() {
var holder = super.getHolder();
assert holder != null;
return holder;
}
// TODO: Visit decorators with arguments
@Override
public void visitPyCallExpression(@NotNull PyCallExpression node) {
@@ -83,32 +92,38 @@ public class PyTypeCheckerInspection extends PyInspection {
PyAnnotation annotation = function.getAnnotation();
String typeCommentAnnotation = function.getTypeCommentAnnotation();
if (annotation != null || typeCommentAnnotation != null) {
PyType expected = getExpectedReturnType(function, myTypeEvalContext);
if (expected == null) return;
// We cannot just match annotated and inferred types, as we cannot promote inferred to Literal
PyExpression returnExpr = node.getExpression();
PyType expected = getExpectedReturnType(function);
if (returnExpr == null && !(expected instanceof PyNoneType) && PyTypeChecker.match(expected, PyNoneType.INSTANCE, myTypeEvalContext)) {
final String expectedName = PythonDocumentationProvider.getVerboseTypeName(expected, myTypeEvalContext);
getHolder()
.problem(node, PyPsiBundle.message("INSP.type.checker.returning.type.has.implicit.return", expectedName))
.fix(new PyMakeReturnsExplicitFix(function))
.register();
return;
}
PyType actual = returnExpr != null ? tryPromotingType(returnExpr, expected) : PyNoneType.INSTANCE;
if (expected != null && actual instanceof PyTypedDictType) {
if (actual instanceof PyTypedDictType) {
if (reportTypedDictProblems(expected, (PyTypedDictType)actual, returnExpr)) return;
}
if (!PyTypeChecker.match(expected, actual, myTypeEvalContext)) {
String expectedName = PythonDocumentationProvider.getVerboseTypeName(expected, myTypeEvalContext);
String actualName = PythonDocumentationProvider.getTypeName(actual, myTypeEvalContext);
var localQuickFix = new PyMakeFunctionReturnTypeQuickFix(function, returnExpr, actual, myTypeEvalContext);
var globalQuickFix = new PyMakeFunctionReturnTypeQuickFix(function, returnExpr, null, myTypeEvalContext);
registerProblem(returnExpr != null ? returnExpr : node,
PyPsiBundle.message("INSP.type.checker.expected.type.got.type.instead", expectedName, actualName),
localQuickFix, globalQuickFix);
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))
.fix(new PyMakeFunctionReturnTypeQuickFix(function, myTypeEvalContext))
.register();
}
}
}
}
@Nullable
private PyType getExpectedReturnType(@NotNull PyFunction function) {
return getExpectedReturnType(function, myTypeEvalContext);
}
@Nullable
public static PyType getExpectedReturnType(@NotNull PyFunction function, @NotNull TypeEvalContext typeEvalContext) {
final PyType returnType = typeEvalContext.getReturnType(function);
@@ -120,13 +135,6 @@ public class PyTypeCheckerInspection extends PyInspection {
return returnType;
}
@Nullable
public static PyType getActualReturnType(@NotNull PyFunction function, @Nullable PyExpression returnExpr,
@NotNull TypeEvalContext context) {
PyType returnTypeExpected = getExpectedReturnType(function, context);
return returnExpr != null ? tryPromotingType(returnExpr, returnTypeExpected, context) : PyNoneType.INSTANCE;
}
@Override
public void visitPyTargetExpression(@NotNull PyTargetExpression node) {
// TODO: Check types in class-level assignments
@@ -230,21 +238,28 @@ public class PyTypeCheckerInspection extends PyInspection {
final PyAnnotation annotation = node.getAnnotation();
final String typeCommentAnnotation = node.getTypeCommentAnnotation();
if (annotation != null || typeCommentAnnotation != null) {
if (!PyUtil.isEmptyFunction(node)) {
final ReturnVisitor visitor = new ReturnVisitor(node);
node.getStatementList().accept(visitor);
if (!visitor.myHasReturns) {
final PyType expected = getExpectedReturnType(node);
final String expectedName = PythonDocumentationProvider.getTypeName(expected, myTypeEvalContext);
if (expected != null && !(expected instanceof PyNoneType)) {
registerProblem(annotation != null ? annotation.getValue() : node.getTypeComment(),
PyPsiBundle.message("INSP.type.checker.expected.to.return.type.got.no.return", expectedName));
final PyType expected = getExpectedReturnType(node, myTypeEvalContext);
final boolean returnsNone = expected instanceof PyNoneType;
final boolean returnsOptional = PyTypeChecker.match(expected, PyNoneType.INSTANCE, myTypeEvalContext);
if (expected != null && !returnsOptional && !PyUtil.isEmptyFunction(node)) {
final List<PyStatement> returnPoints = node.getReturnPoints(myTypeEvalContext);
final boolean hasImplicitReturns = exists(returnPoints, it -> !(it instanceof PyReturnStatement));
if (hasImplicitReturns) {
final String expectedName = PythonDocumentationProvider.getVerboseTypeName(expected, 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))
.fix(new PyMakeFunctionReturnTypeQuickFix(node, myTypeEvalContext))
.register();
}
}
}
if (PyUtil.isInitMethod(node) && !(getExpectedReturnType(node) instanceof PyNoneType
|| PyTypingTypeProvider.isNoReturn(node, myTypeEvalContext))) {
if (PyUtil.isInitMethod(node) && !(returnsNone || PyTypingTypeProvider.isNoReturn(node, myTypeEvalContext))) {
registerProblem(annotation != null ? annotation.getValue() : node.getTypeComment(),
PyPsiBundle.message("INSP.type.checker.init.should.return.none"));
}
@@ -260,29 +275,6 @@ public class PyTypeCheckerInspection extends PyInspection {
}
}
private static class ReturnVisitor extends PyRecursiveElementVisitor {
private final PyFunction myFunction;
private boolean myHasReturns = false;
ReturnVisitor(PyFunction function) {
myFunction = function;
}
@Override
public void visitPyYieldExpression(@NotNull PyYieldExpression node) {
if (ScopeUtil.getScopeOwner(node) == myFunction) {
myHasReturns = true;
}
}
@Override
public void visitPyReturnStatement(@NotNull PyReturnStatement node) {
if (ScopeUtil.getScopeOwner(node) == myFunction) {
myHasReturns = true;
}
}
}
private void checkCallSite(@NotNull PyCallSiteExpression callSite) {
final List<AnalyzeCalleeResults> calleesResults = StreamEx
.of(mapArguments(callSite, getResolveContext()))
@@ -514,7 +506,7 @@ public class PyTypeCheckerInspection extends PyInspection {
}
private static boolean matchedCalleeResultsExist(@NotNull List<AnalyzeCalleeResults> calleesResults) {
return ContainerUtil.exists(calleesResults, calleeResults ->
return exists(calleesResults, calleeResults ->
ContainerUtil.all(calleeResults.getResults(), AnalyzeArgumentResult::isMatched) &&
calleeResults.getUnmatchedArguments().isEmpty() &&
calleeResults.getUnmatchedParameters().isEmpty() &&

View File

@@ -32,52 +32,27 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import static com.jetbrains.python.inspections.PyTypeCheckerInspection.Visitor.getActualReturnType;
/**
* @author lada
*/
public class PyMakeFunctionReturnTypeQuickFix implements LocalQuickFix {
private final @NotNull SmartPsiElementPointer<PyFunction> myFunction;
private final @Nullable SmartPsiElementPointer<PyExpression> myReturnExpr;
private final @Nullable SmartPsiElementPointer<PyAnnotation> myAnnotation;
private final @Nullable SmartPsiElementPointer<PsiComment> myTypeCommentAnnotation;
private final String myReturnTypeName;
private final boolean myHaveSuggestedType;
public PyMakeFunctionReturnTypeQuickFix(@NotNull PyFunction function,
@Nullable PyExpression returnExpr,
@Nullable PyType suggestedReturnType,
@NotNull TypeEvalContext context) {
this(function,
returnExpr,
function.getAnnotation(),
function.getTypeComment(),
suggestedReturnType != null,
getReturnTypeName(function, suggestedReturnType, context));
public PyMakeFunctionReturnTypeQuickFix(@NotNull PyFunction function, @NotNull TypeEvalContext context) {
this(function, getReturnTypeName(function, context));
}
private PyMakeFunctionReturnTypeQuickFix(@NotNull PyFunction function, @NotNull String returnTypeName) {
SmartPointerManager manager = SmartPointerManager.getInstance(function.getProject());
myFunction = manager.createSmartPsiElementPointer(function);
myReturnTypeName = returnTypeName;
}
@NotNull
private static String getReturnTypeName(@NotNull PyFunction function, @Nullable PyType returnType, @NotNull TypeEvalContext context) {
PyType type = returnType != null ? returnType : function.getReturnStatementType(context);
private static String getReturnTypeName(@NotNull PyFunction function, @NotNull TypeEvalContext context) {
final PyType type = function.getInferredReturnType(context);
return PythonDocumentationProvider.getTypeHint(type, context);
}
private PyMakeFunctionReturnTypeQuickFix(@NotNull PyFunction function,
@Nullable PyExpression returnExpr,
@Nullable PyAnnotation annotation,
@Nullable PsiComment typeComment,
boolean returnTypeSuggested,
@NotNull String returnTypeName) {
SmartPointerManager manager = SmartPointerManager.getInstance(function.getProject());
myFunction = manager.createSmartPsiElementPointer(function);
myReturnExpr = returnExpr != null ? manager.createSmartPsiElementPointer(returnExpr) : null;
myAnnotation = annotation != null ? manager.createSmartPsiElementPointer(annotation) : null;
myTypeCommentAnnotation = typeComment != null ? manager.createSmartPsiElementPointer(typeComment) : null;
myHaveSuggestedType = returnTypeSuggested;
myReturnTypeName = returnTypeName;
}
@Override
@NotNull
public String getName() {
@@ -94,51 +69,46 @@ public class PyMakeFunctionReturnTypeQuickFix implements LocalQuickFix {
@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
if (myAnnotation != null) {
PyAnnotation annotation = myAnnotation.getElement();
if (annotation != null) {
PyExpression annotationExpr = annotation.getValue();
if (annotationExpr == null) return;
PsiElement newElement =
annotationExpr.replace(elementGenerator.createExpressionFromText(LanguageLevel.PYTHON34, myReturnTypeName));
addImportsForTypeAnnotations(newElement);
final PyFunction function = myFunction.getElement();
if (function == null) return;
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(project);
boolean shouldAddImports = false;
PyAnnotation annotation = function.getAnnotation();
if (annotation != null) {
PyExpression annotationExpr = annotation.getValue();
if (annotationExpr != null) {
annotationExpr.replace(elementGenerator.createExpressionFromText(LanguageLevel.PYTHON34, myReturnTypeName));
shouldAddImports = true;
}
}
else if (myTypeCommentAnnotation != null) {
PsiComment typeComment = myTypeCommentAnnotation.getElement();
if (typeComment != null) {
StringBuilder typeCommentAnnotation = new StringBuilder(typeComment.getText());
typeCommentAnnotation.delete(typeCommentAnnotation.indexOf("->"), typeCommentAnnotation.length());
typeCommentAnnotation.append("-> ").append(myReturnTypeName);
PsiComment newTypeComment =
elementGenerator.createFromText(LanguageLevel.PYTHON27, PsiComment.class, typeCommentAnnotation.toString());
PsiElement newElement = typeComment.replace(newTypeComment);
addImportsForTypeAnnotations(newElement);
}
PsiComment typeComment = function.getTypeComment();
if (typeComment != null) {
StringBuilder typeCommentAnnotation = new StringBuilder(typeComment.getText());
typeCommentAnnotation.delete(typeCommentAnnotation.indexOf("->"), typeCommentAnnotation.length());
typeCommentAnnotation.append("-> ").append(myReturnTypeName);
typeComment.replace(
elementGenerator.createFromText(LanguageLevel.PYTHON27, PsiComment.class, typeCommentAnnotation.toString()));
shouldAddImports = true;
}
if (shouldAddImports) {
addImportsForTypeAnnotations(TypeEvalContext.userInitiated(project, function.getContainingFile()));
}
}
private void addImportsForTypeAnnotations(@NotNull PsiElement element) {
PsiFile file = element.getContainingFile();
if (file == null) return;
private void addImportsForTypeAnnotations(@NotNull TypeEvalContext context) {
PyFunction function = myFunction.getElement();
if (function == null) return;
Project project = element.getProject();
TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(project, file);
PyType typeForImports = getTypeForImports(function, typeEvalContext);
PsiFile file = function.getContainingFile();
if (file == null) return;
PyType typeForImports = function.getInferredReturnType(context);
if (typeForImports != null) {
PyTypeHintGenerationUtil.addImportsForTypeAnnotations(List.of(typeForImports), typeEvalContext, file);
}
}
private @Nullable PyType getTypeForImports(@NotNull PyFunction function, @NotNull TypeEvalContext context) {
PyType returnTypeActual = getActualReturnType(function, myReturnExpr != null ? myReturnExpr.getElement() : null, context);
if (myHaveSuggestedType && returnTypeActual != null) {
return returnTypeActual;
}
else {
return function.getReturnStatementType(context);
PyTypeHintGenerationUtil.addImportsForTypeAnnotations(List.of(typeForImports), context, file);
}
}
@@ -148,18 +118,6 @@ public class PyMakeFunctionReturnTypeQuickFix implements LocalQuickFix {
if (function == null) {
return null;
}
@Nullable PyExpression returnExpr = PyRefactoringUtil.findSameElementForPreview(myReturnExpr, target);
if (myReturnExpr != null && returnExpr == null) {
return null;
}
@Nullable PyAnnotation annotation = PyRefactoringUtil.findSameElementForPreview(myAnnotation, target);
if (myAnnotation != null && annotation == null) {
return null;
}
@Nullable PsiComment typeComment = PyRefactoringUtil.findSameElementForPreview(myTypeCommentAnnotation, target);
if (myTypeCommentAnnotation != null && typeComment == null) {
return null;
}
return new PyMakeFunctionReturnTypeQuickFix(function, returnExpr, annotation, typeComment, myHaveSuggestedType, myReturnTypeName);
return new PyMakeFunctionReturnTypeQuickFix(function, myReturnTypeName);
}
}

View File

@@ -0,0 +1,46 @@
package com.jetbrains.python.inspections.quickfix;
import com.intellij.modcommand.ActionContext;
import com.intellij.modcommand.ModPsiUpdater;
import com.intellij.modcommand.PsiUpdateModCommandAction;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
/**
* Appends missing {@code return None}, and transforms {@code return} into {@code return None}.
*/
public class PyMakeReturnsExplicitFix extends PsiUpdateModCommandAction<PyFunction> {
public PyMakeReturnsExplicitFix(@NotNull PyFunction function) {
super(function);
}
@Override
protected void invoke(@NotNull ActionContext context, @NotNull PyFunction element, @NotNull ModPsiUpdater updater) {
var returnPoints = element.getReturnPoints(TypeEvalContext.userInitiated(element.getProject(), element.getContainingFile()));
for (var point : returnPoints) {
makeExplicit(point);
}
}
@Override
public @NotNull String getFamilyName() {
return PyPsiBundle.message("QFIX.NAME.make.return.stmts.explicit");
}
private static void makeExplicit(@NotNull PyStatement stmt) {
PyElementGenerator elementGenerator = PyElementGenerator.getInstance(stmt.getProject());
LanguageLevel languageLevel = LanguageLevel.forElement(stmt);
var returnStmt = elementGenerator.createFromText(languageLevel, PyReturnStatement.class, "return None");
if ((stmt instanceof PyReturnStatement ret && ret.getExpression() == null) || (stmt instanceof PyPassStatement)) {
stmt.replace(returnStmt);
}
else if (!(stmt instanceof PyReturnStatement)) {
stmt.getParent().addAfter(returnStmt, stmt);
}
}
}

View File

@@ -1,6 +1,8 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.jetbrains.python.psi.impl;
import com.intellij.codeInsight.controlflow.ControlFlowUtil;
import com.intellij.codeInsight.controlflow.Instruction;
import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.util.Key;
@@ -21,6 +23,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyStubElementTypes;
import com.jetbrains.python.codeInsight.controlflow.CallInstruction;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
@@ -153,7 +156,12 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
return PyTypingTypeProvider.removeNarrowedTypeIfNeeded(derefType(returnTypeRef, typeProvider));
}
}
return getInferredReturnType(context);
}
@Override
public @Nullable PyType getInferredReturnType(@NotNull TypeEvalContext context) {
PyType inferredType = null;
if (context.allowReturnTypes(this)) {
final Ref<? extends PyType> yieldTypeRef = getYieldStatementType(context);
@@ -342,16 +350,54 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
@Override
public @Nullable PyType getReturnStatementType(@NotNull TypeEvalContext context) {
final ReturnVisitor visitor = new ReturnVisitor(this, context);
final PyStatementList statements = getStatementList();
statements.accept(visitor);
if ((isGeneratedStub() || PyKnownDecoratorUtil.hasAbstractDecorator(this, context)) && !visitor.myHasReturns) {
return PyUtil.getNullableParameterizedCachedValue(this, context, (it) -> getReturnStatementTypeNoCache(it));
}
private @Nullable PyType getReturnStatementTypeNoCache(@NotNull TypeEvalContext context) {
final List<PyStatement> returnPoints = getReturnPoints(context);
final List<PyType> types = new ArrayList<>();
boolean hasReturn = false;
for (var point : returnPoints) {
if (point instanceof PyReturnStatement returnStatement) {
hasReturn = true;
final PyExpression expr = returnStatement.getExpression();
types.add(expr != null ? context.getType(expr) : PyNoneType.INSTANCE);
}
else {
types.add(PyNoneType.INSTANCE);
}
}
if ((isGeneratedStub() || PyKnownDecoratorUtil.hasAbstractDecorator(this, context)) && !hasReturn) {
if (PyUtil.isInitMethod(this)) {
return PyNoneType.INSTANCE;
}
return null;
}
return visitor.result();
return PyUnionType.union(types);
}
@Override
public @NotNull List<PyStatement> getReturnPoints(@NotNull TypeEvalContext context) {
final Instruction[] flow = ControlFlowCache.getControlFlow(this).getInstructions();
final List<PyStatement> returnPoints = new ArrayList<>();
ControlFlowUtil.iteratePrev(flow.length-1, flow, instruction -> {
if (instruction instanceof CallInstruction ci && ci.isNoReturnCall(context)) {
return ControlFlowUtil.Operation.CONTINUE;
}
final PsiElement element = instruction.getElement();
if (!(element instanceof PyStatement statement)) {
return ControlFlowUtil.Operation.NEXT;
}
if (element instanceof PyRaiseStatement) {
return ControlFlowUtil.Operation.CONTINUE;
}
returnPoints.add(statement);
return ControlFlowUtil.Operation.CONTINUE;
});
return returnPoints;
}
@Override
@@ -412,44 +458,6 @@ public class PyFunctionImpl extends PyBaseElementImpl<PyFunctionStub> implements
return false;
}
private static final class ReturnVisitor extends PyRecursiveElementVisitor {
private final PyFunction myFunction;
private final TypeEvalContext myContext;
private PyType myResult = null;
private boolean myHasReturns = false;
private boolean myHasRaises = false;
private ReturnVisitor(PyFunction function, final TypeEvalContext context) {
myFunction = function;
myContext = context;
}
@Override
public void visitPyReturnStatement(@NotNull PyReturnStatement node) {
if (ScopeUtil.getScopeOwner(node) == myFunction) {
final PyExpression expr = node.getExpression();
PyType returnType = expr == null ? PyNoneType.INSTANCE : myContext.getType(expr);
if (!myHasReturns) {
myResult = returnType;
myHasReturns = true;
}
else {
myResult = PyUnionType.union(myResult, returnType);
}
}
}
@Override
public void visitPyRaiseStatement(@NotNull PyRaiseStatement node) {
myHasRaises = true;
}
@Nullable
PyType result() {
return myHasReturns || myHasRaises ? myResult : PyNoneType.INSTANCE;
}
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyFunction(this);

View File

@@ -16,6 +16,7 @@
package com.jetbrains.python.psi.impl;
import com.intellij.lang.ASTNode;
import com.jetbrains.python.psi.PyElementVisitor;
import com.jetbrains.python.psi.PyPassStatement;
@@ -23,4 +24,9 @@ public class PyPassStatementImpl extends PyElementImpl implements PyPassStatemen
public PyPassStatementImpl(ASTNode astNode) {
super(astNode);
}
@Override
protected void acceptPyVisitor(PyElementVisitor pyVisitor) {
pyVisitor.visitPyPassStatement(this);
}
}