diff --git a/crates/ty_python_semantic/resources/mdtest/function/return_type.md b/crates/ty_python_semantic/resources/mdtest/function/return_type.md index 93d4843b70c8c..1ca2b10dca9b4 100644 --- a/crates/ty_python_semantic/resources/mdtest/function/return_type.md +++ b/crates/ty_python_semantic/resources/mdtest/function/return_type.md @@ -278,6 +278,20 @@ def f(cond: bool) -> int: return 2 ``` +## Invalid implicit return type always None + + + +If the function has no `return` statement or if it has only bare `return` statement (no variable in +the return statement), then we show a diagnostic hint that the return annotation should be `-> None` +or a `return` statement should be added. + +```py +# error: [invalid-return-type] +def f() -> int: + print("hello") +``` + ## NotImplemented ### Default Python version diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" index 67c96c3139e5f..d9a0e67f6d459 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Diagnostics_for_`inv\342\200\246_(35563a74094b14d5).snap" @@ -24,13 +24,14 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `str` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `str` --> src/mdtest_snippet.py:7:25 | 6 | class Concrete(Abstract): 7 | def method(self) -> str: ... # error: [invalid-return-type] | ^^^ | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: Only functions in stub files, methods on protocol classes, or methods with `@abstractmethod` are permitted to have empty bodies info: Class `Concrete` has `typing.Protocol` in its MRO, but it is not a protocol class info: Only classes that directly inherit from `typing.Protocol` or `typing_extensions.Protocol` are considered protocol classes diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" index e77bd91b65ba5..889888f0a30ca 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(393cb38bf7119649).snap" @@ -71,7 +71,7 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:12:22 | 11 | # error: [invalid-return-type] @@ -80,6 +80,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 13 | if cond: 14 | raise ValueError() | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" new file mode 100644 index 0000000000000..a4fd88d692c93 --- /dev/null +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_implicit_ret\342\200\246_(3d2d19aa49b28f1c).snap" @@ -0,0 +1,34 @@ +--- +source: crates/ty_test/src/lib.rs +expression: snapshot +--- +--- +mdtest name: return_type.md - Function return type - Invalid implicit return type always None +mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md +--- + +# Python source files + +## mdtest_snippet.py + +``` +1 | # error: [invalid-return-type] +2 | def f() -> int: +3 | print("hello") +``` + +# Diagnostics + +``` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` + --> src/mdtest_snippet.py:2:12 + | +1 | # error: [invalid-return-type] +2 | def f() -> int: + | ^^^ +3 | print("hello") + | +info: Consider changing the return annotation to `-> None` or adding a `return` statement +info: rule `invalid-return-type` is enabled by default + +``` diff --git a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap index d452f178202ba..4f36ded37648c 100644 --- a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap +++ b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_(a91e0c67519cd77f).snap @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/function/return_type.md # Diagnostics ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.py:2:12 | 1 | # error: [invalid-return-type] @@ -43,6 +43,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not | ^^^ 3 | 1 | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` @@ -84,13 +85,14 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `T` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `T` --> src/mdtest_snippet.py:18:16 | 17 | # error: [invalid-return-type] 18 | def m(x: T) -> T: ... | ^ | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" index bf469ce066d66..6f834b61e3637 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/return_type.md_-_Function_return_type_-_Invalid_return_type_\342\200\246_(c3a523878447af6b).snap" @@ -46,7 +46,7 @@ info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:6:14 | 5 | # error: [invalid-return-type] @@ -55,12 +55,13 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 7 | print("...") 8 | ... | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` ``` -error[invalid-return-type]: Function can implicitly return `None`, which is not assignable to return type `int` +error[invalid-return-type]: Function always implicitly returns `None`, which is not assignable to return type `int` --> src/mdtest_snippet.pyi:11:14 | 10 | # error: [invalid-return-type] @@ -69,6 +70,7 @@ error[invalid-return-type]: Function can implicitly return `None`, which is not 12 | f"""{foo} is a function that ...""" 13 | ... | +info: Consider changing the return annotation to `-> None` or adding a `return` statement info: rule `invalid-return-type` is enabled by default ``` diff --git a/crates/ty_python_semantic/src/types/diagnostic.rs b/crates/ty_python_semantic/src/types/diagnostic.rs index c996be3d0bf10..fb6937c3e0e25 100644 --- a/crates/ty_python_semantic/src/types/diagnostic.rs +++ b/crates/ty_python_semantic/src/types/diagnostic.rs @@ -1684,15 +1684,29 @@ pub(super) fn report_implicit_return_type( expected_ty: Type, has_empty_body: bool, enclosing_class_of_method: Option, + no_return: bool, ) { let Some(builder) = context.report_lint(&INVALID_RETURN_TYPE, range) else { return; }; let db = context.db(); - let mut diagnostic = builder.into_diagnostic(format_args!( - "Function can implicitly return `None`, which is not assignable to return type `{}`", - expected_ty.display(db) - )); + + // If no return statement is defined in the function, then the function always returns `None` + let mut diagnostic = if no_return { + let mut diag = builder.into_diagnostic(format_args!( + "Function always implicitly returns `None`, which is not assignable to return type `{}`", + expected_ty.display(db), + )); + diag.info( + "Consider changing the return annotation to `-> None` or adding a `return` statement", + ); + diag + } else { + builder.into_diagnostic(format_args!( + "Function can implicitly return `None`, which is not assignable to return type `{}`", + expected_ty.display(db), + )) + }; if !has_empty_body { return; } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b60ac4c9ba094..21e6a01435d6f 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -1853,12 +1853,14 @@ impl<'db> TypeInferenceBuilder<'db> { if use_def.can_implicit_return(self.db()) && !Type::none(self.db()).is_assignable_to(self.db(), declared_ty) { + let no_return = self.return_types_and_ranges.is_empty(); report_implicit_return_type( &self.context, returns.range(), declared_ty, has_empty_body, enclosing_class_context, + no_return, ); } }