1818import com .jetbrains .python .codeInsight .typing .PyTypingTypeProvider ;
1919import com .jetbrains .python .documentation .PythonDocumentationProvider ;
2020import com .jetbrains .python .inspections .quickfix .PyMakeFunctionReturnTypeQuickFix ;
21+ import com .jetbrains .python .inspections .quickfix .PyMakeReturnsExplicitFix ;
2122import com .jetbrains .python .psi .*;
2223import com .jetbrains .python .psi .resolve .PyResolveContext ;
2324import com .jetbrains .python .psi .types .*;
2728
2829import java .util .*;
2930
31+ import static com .intellij .util .containers .ContainerUtil .exists ;
3032import static com .jetbrains .python .psi .PyUtil .as ;
3133import static com .jetbrains .python .psi .impl .PyCallExpressionHelper .*;
3234
@@ -44,10 +46,17 @@ public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean is
4446 }
4547
4648 public static class Visitor extends PyInspectionVisitor {
47- public Visitor (@ Nullable ProblemsHolder holder , @ NotNull TypeEvalContext context ) {
49+ public Visitor (@ NotNull ProblemsHolder holder , @ NotNull TypeEvalContext context ) {
4850 super (holder , context );
4951 }
5052
53+ @ Override
54+ protected @ NotNull ProblemsHolder getHolder () {
55+ var holder = super .getHolder ();
56+ assert holder != null ;
57+ return holder ;
58+ }
59+
5160 // TODO: Visit decorators with arguments
5261 @ Override
5362 public void visitPyCallExpression (@ NotNull PyCallExpression node ) {
@@ -83,32 +92,38 @@ public void visitPyReturnStatement(@NotNull PyReturnStatement node) {
8392 PyAnnotation annotation = function .getAnnotation ();
8493 String typeCommentAnnotation = function .getTypeCommentAnnotation ();
8594 if (annotation != null || typeCommentAnnotation != null ) {
95+ PyType expected = getExpectedReturnType (function , myTypeEvalContext );
96+ if (expected == null ) return ;
97+
98+ // We cannot just match annotated and inferred types, as we cannot promote inferred to Literal
8699 PyExpression returnExpr = node .getExpression ();
87- PyType expected = getExpectedReturnType (function );
100+ if (returnExpr == null && !(expected instanceof PyNoneType ) && PyTypeChecker .match (expected , PyNoneType .INSTANCE , myTypeEvalContext )) {
101+ final String expectedName = PythonDocumentationProvider .getVerboseTypeName (expected , myTypeEvalContext );
102+ getHolder ()
103+ .problem (node , PyPsiBundle .message ("INSP.type.checker.returning.type.has.implicit.return" , expectedName ))
104+ .fix (new PyMakeReturnsExplicitFix (function ))
105+ .register ();
106+ return ;
107+ }
108+
88109 PyType actual = returnExpr != null ? tryPromotingType (returnExpr , expected ) : PyNoneType .INSTANCE ;
89110
90- if (expected != null && actual instanceof PyTypedDictType ) {
111+ if (actual instanceof PyTypedDictType ) {
91112 if (reportTypedDictProblems (expected , (PyTypedDictType )actual , returnExpr )) return ;
92113 }
93114
94115 if (!PyTypeChecker .match (expected , actual , myTypeEvalContext )) {
95- String expectedName = PythonDocumentationProvider .getVerboseTypeName (expected , myTypeEvalContext );
96- String actualName = PythonDocumentationProvider .getTypeName (actual , myTypeEvalContext );
97- var localQuickFix = new PyMakeFunctionReturnTypeQuickFix (function , returnExpr , actual , myTypeEvalContext );
98- var globalQuickFix = new PyMakeFunctionReturnTypeQuickFix (function , returnExpr , null , myTypeEvalContext );
99- registerProblem (returnExpr != null ? returnExpr : node ,
100- PyPsiBundle .message ("INSP.type.checker.expected.type.got.type.instead" , expectedName , actualName ),
101- localQuickFix , globalQuickFix );
116+ final String expectedName = PythonDocumentationProvider .getVerboseTypeName (expected , myTypeEvalContext );
117+ final String actualName = PythonDocumentationProvider .getTypeName (actual , myTypeEvalContext );
118+ getHolder ()
119+ .problem (returnExpr != null ? returnExpr : node , PyPsiBundle .message ("INSP.type.checker.expected.type.got.type.instead" , expectedName , actualName ))
120+ .fix (new PyMakeFunctionReturnTypeQuickFix (function , myTypeEvalContext ))
121+ .register ();
102122 }
103123 }
104124 }
105125 }
106126
107- @ Nullable
108- private PyType getExpectedReturnType (@ NotNull PyFunction function ) {
109- return getExpectedReturnType (function , myTypeEvalContext );
110- }
111-
112127 @ Nullable
113128 public static PyType getExpectedReturnType (@ NotNull PyFunction function , @ NotNull TypeEvalContext typeEvalContext ) {
114129 final PyType returnType = typeEvalContext .getReturnType (function );
@@ -120,13 +135,6 @@ public static PyType getExpectedReturnType(@NotNull PyFunction function, @NotNul
120135 return returnType ;
121136 }
122137
123- @ Nullable
124- public static PyType getActualReturnType (@ NotNull PyFunction function , @ Nullable PyExpression returnExpr ,
125- @ NotNull TypeEvalContext context ) {
126- PyType returnTypeExpected = getExpectedReturnType (function , context );
127- return returnExpr != null ? tryPromotingType (returnExpr , returnTypeExpected , context ) : PyNoneType .INSTANCE ;
128- }
129-
130138 @ Override
131139 public void visitPyTargetExpression (@ NotNull PyTargetExpression node ) {
132140 // TODO: Check types in class-level assignments
@@ -230,21 +238,28 @@ public void visitPyFunction(@NotNull PyFunction node) {
230238 final PyAnnotation annotation = node .getAnnotation ();
231239 final String typeCommentAnnotation = node .getTypeCommentAnnotation ();
232240 if (annotation != null || typeCommentAnnotation != null ) {
233- if (!PyUtil .isEmptyFunction (node )) {
234- final ReturnVisitor visitor = new ReturnVisitor (node );
235- node .getStatementList ().accept (visitor );
236- if (!visitor .myHasReturns ) {
237- final PyType expected = getExpectedReturnType (node );
238- final String expectedName = PythonDocumentationProvider .getTypeName (expected , myTypeEvalContext );
239- if (expected != null && !(expected instanceof PyNoneType )) {
240- registerProblem (annotation != null ? annotation .getValue () : node .getTypeComment (),
241- PyPsiBundle .message ("INSP.type.checker.expected.to.return.type.got.no.return" , expectedName ));
241+ final PyType expected = getExpectedReturnType (node , myTypeEvalContext );
242+ final boolean returnsNone = expected instanceof PyNoneType ;
243+ final boolean returnsOptional = PyTypeChecker .match (expected , PyNoneType .INSTANCE , myTypeEvalContext );
244+
245+ if (expected != null && !returnsOptional && !PyUtil .isEmptyFunction (node )) {
246+ final List <PyStatement > returnPoints = node .getReturnPoints (myTypeEvalContext );
247+ final boolean hasImplicitReturns = exists (returnPoints , it -> !(it instanceof PyReturnStatement ));
248+
249+ if (hasImplicitReturns ) {
250+ final String expectedName = PythonDocumentationProvider .getVerboseTypeName (expected , myTypeEvalContext );
251+ final String actualName = PythonDocumentationProvider .getTypeName (node .getReturnStatementType (myTypeEvalContext ), myTypeEvalContext );
252+ final PsiElement annotationValue = annotation != null ? annotation .getValue () : node .getTypeComment ();
253+ if (annotationValue != null ) {
254+ getHolder ()
255+ .problem (annotationValue , PyPsiBundle .message ("INSP.type.checker.expected.type.got.type.instead" , expectedName , actualName ))
256+ .fix (new PyMakeFunctionReturnTypeQuickFix (node , myTypeEvalContext ))
257+ .register ();
242258 }
243259 }
244260 }
245261
246- if (PyUtil .isInitMethod (node ) && !(getExpectedReturnType (node ) instanceof PyNoneType
247- || PyTypingTypeProvider .isNoReturn (node , myTypeEvalContext ))) {
262+ if (PyUtil .isInitMethod (node ) && !(returnsNone || PyTypingTypeProvider .isNoReturn (node , myTypeEvalContext ))) {
248263 registerProblem (annotation != null ? annotation .getValue () : node .getTypeComment (),
249264 PyPsiBundle .message ("INSP.type.checker.init.should.return.none" ));
250265 }
@@ -260,29 +275,6 @@ public void visitPyComprehensionElement(@NotNull PyComprehensionElement node) {
260275 }
261276 }
262277
263- private static class ReturnVisitor extends PyRecursiveElementVisitor {
264- private final PyFunction myFunction ;
265- private boolean myHasReturns = false ;
266-
267- ReturnVisitor (PyFunction function ) {
268- myFunction = function ;
269- }
270-
271- @ Override
272- public void visitPyYieldExpression (@ NotNull PyYieldExpression node ) {
273- if (ScopeUtil .getScopeOwner (node ) == myFunction ) {
274- myHasReturns = true ;
275- }
276- }
277-
278- @ Override
279- public void visitPyReturnStatement (@ NotNull PyReturnStatement node ) {
280- if (ScopeUtil .getScopeOwner (node ) == myFunction ) {
281- myHasReturns = true ;
282- }
283- }
284- }
285-
286278 private void checkCallSite (@ NotNull PyCallSiteExpression callSite ) {
287279 final List <AnalyzeCalleeResults > calleesResults = StreamEx
288280 .of (mapArguments (callSite , getResolveContext ()))
@@ -513,7 +505,7 @@ private PyType substituteGenerics(@Nullable PyType expectedArgumentType, @NotNul
513505 }
514506
515507 private static boolean matchedCalleeResultsExist (@ NotNull List <AnalyzeCalleeResults > calleesResults ) {
516- return ContainerUtil . exists (calleesResults , calleeResults ->
508+ return exists (calleesResults , calleeResults ->
517509 ContainerUtil .all (calleeResults .getResults (), AnalyzeArgumentResult ::isMatched ) &&
518510 calleeResults .getUnmatchedArguments ().isEmpty () &&
519511 calleeResults .getUnmatchedParameters ().isEmpty () &&
0 commit comments