mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:38:25 +00:00
[ty] Add diagnosis for function with no return statement but with return type annotation (#18359)
## Summary Partially implement https://github.com/astral-sh/ty/issues/538, ```py from pathlib import Path def setup_test_project(registry_name: str, registry_url: str, project_dir: str) -> Path: pyproject_file = Path(project_dir) / "pyproject.toml" pyproject_file.write_text("...", encoding="utf-8") ``` As no return statement is defined in the function `setup_test_project` with annotated return type `Path`, we provide the following diagnosis : - error[invalid-return-type]: Function **always** implicitly returns `None`, which is not assignable to return type `Path` with a subdiagnostic : - note: Consider changing your return annotation to `-> None` or adding a `return` statement ## Test Plan mdtests with snapshots to capture the subdiagnostic. I have to mention that existing snapshots were modified since they now fall in this category. --------- Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
parent
3445d1322d
commit
695de4f27f
8 changed files with 80 additions and 10 deletions
|
@ -278,6 +278,20 @@ def f(cond: bool) -> int:
|
|||
return 2
|
||||
```
|
||||
|
||||
## Invalid implicit return type always None
|
||||
|
||||
<!-- snapshot-diagnostics -->
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
||||
```
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
|
@ -1685,15 +1685,29 @@ pub(super) fn report_implicit_return_type(
|
|||
expected_ty: Type,
|
||||
has_empty_body: bool,
|
||||
enclosing_class_of_method: Option<ClassLiteral>,
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1872,12 +1872,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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue