mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 08:51:02 +07:00
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:
committed by
intellij-monorepo-bot
parent
137b1d2b13
commit
4dd41ee9f5
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() &&
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user