mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +00:00
[ty] introduce multiline pretty printer (#19979)
Requires some iteration, but this includes the most tedious part -- threading a new concept of DisplaySettings through every type display impl. Currently it only holds a boolean for multiline, but in the future it could also take other things like "render to markdown" or "here's your base indent if you make a newline". For types which have exposed display functions I've left the old signature as a compatibility polyfill to avoid having to audit everywhere that prints types right off the bat (notably I originally tried doing multiline functions unconditionally and a ton of things churned that clearly weren't ready for multi-line (diagnostics). The only real use of this API in this PR is to multiline render function types in hovers, which is the highest impact (see snapshot changes). Fixes https://github.com/astral-sh/ty/issues/1000
This commit is contained in:
parent
59b078b1bf
commit
c6dcfe36d0
4 changed files with 793 additions and 106 deletions
|
@ -6,8 +6,8 @@ use ruff_db::parsed::parsed_module;
|
||||||
use ruff_text_size::{Ranged, TextSize};
|
use ruff_text_size::{Ranged, TextSize};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use ty_python_semantic::SemanticModel;
|
|
||||||
use ty_python_semantic::types::Type;
|
use ty_python_semantic::types::Type;
|
||||||
|
use ty_python_semantic::{DisplaySettings, SemanticModel};
|
||||||
|
|
||||||
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
|
pub fn hover(db: &dyn Db, file: File, offset: TextSize) -> Option<RangedValue<Hover<'_>>> {
|
||||||
let parsed = parsed_module(db, file).load(db);
|
let parsed = parsed_module(db, file).load(db);
|
||||||
|
@ -135,7 +135,10 @@ impl fmt::Display for DisplayHoverContent<'_, '_> {
|
||||||
match self.content {
|
match self.content {
|
||||||
HoverContent::Type(ty) => self
|
HoverContent::Type(ty) => self
|
||||||
.kind
|
.kind
|
||||||
.fenced_code_block(ty.display(self.db), "python")
|
.fenced_code_block(
|
||||||
|
ty.display_with(self.db, DisplaySettings::default().multiline()),
|
||||||
|
"python",
|
||||||
|
)
|
||||||
.fmt(f),
|
.fmt(f),
|
||||||
HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f),
|
HoverContent::Docstring(docstring) => docstring.render(self.kind).fmt(f),
|
||||||
}
|
}
|
||||||
|
@ -201,7 +204,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @r"
|
assert_snapshot!(test.hover(), @r"
|
||||||
def my_func(a, b) -> Unknown
|
def my_func(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
This is such a great func!!
|
This is such a great func!!
|
||||||
|
|
||||||
|
@ -211,7 +217,10 @@ mod tests {
|
||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
```python
|
```python
|
||||||
def my_func(a, b) -> Unknown
|
def my_func(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
```text
|
```text
|
||||||
|
@ -253,7 +262,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @r"
|
assert_snapshot!(test.hover(), @r"
|
||||||
def my_func(a, b) -> Unknown
|
def my_func(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
This is such a great func!!
|
This is such a great func!!
|
||||||
|
|
||||||
|
@ -263,7 +275,10 @@ mod tests {
|
||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
```python
|
```python
|
||||||
def my_func(a, b) -> Unknown
|
def my_func(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
```text
|
```text
|
||||||
|
@ -519,7 +534,10 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @r"
|
assert_snapshot!(test.hover(), @r"
|
||||||
bound method MyClass.my_method(a, b) -> Unknown
|
bound method MyClass.my_method(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
This is such a great func!!
|
This is such a great func!!
|
||||||
|
|
||||||
|
@ -529,7 +547,10 @@ mod tests {
|
||||||
|
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
```python
|
```python
|
||||||
bound method MyClass.my_method(a, b) -> Unknown
|
bound method MyClass.my_method(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
```text
|
```text
|
||||||
|
@ -601,10 +622,16 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_snapshot!(test.hover(), @r"
|
assert_snapshot!(test.hover(), @r"
|
||||||
def foo(a, b) -> Unknown
|
def foo(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
```python
|
```python
|
||||||
def foo(a, b) -> Unknown
|
def foo(
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
```
|
```
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
info[hover]: Hovered content is
|
info[hover]: Hovered content is
|
||||||
|
@ -763,6 +790,128 @@ mod tests {
|
||||||
");
|
");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_overload() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
from typing import overload
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def foo(a: int, b):
|
||||||
|
"""The first overload"""
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def foo(a: str, b):
|
||||||
|
"""The second overload"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if random.choice([True, False]):
|
||||||
|
a = 1
|
||||||
|
else:
|
||||||
|
a = "hello"
|
||||||
|
|
||||||
|
foo<CURSOR>(a, 2)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
(
|
||||||
|
a: int,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
|
(
|
||||||
|
a: str,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
The first overload
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
(
|
||||||
|
a: int,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
|
(
|
||||||
|
a: str,
|
||||||
|
b
|
||||||
|
) -> Unknown
|
||||||
|
```
|
||||||
|
---
|
||||||
|
```text
|
||||||
|
The first overload
|
||||||
|
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:19:13
|
||||||
|
|
|
||||||
|
17 | a = "hello"
|
||||||
|
18 |
|
||||||
|
19 | foo(a, 2)
|
||||||
|
| ^^^- Cursor offset
|
||||||
|
| |
|
||||||
|
| source
|
||||||
|
|
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_overload_compact() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
from typing import overload
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def foo(a: int):
|
||||||
|
"""The first overload"""
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@overload
|
||||||
|
def foo(a: str):
|
||||||
|
"""The second overload"""
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if random.choice([True, False]):
|
||||||
|
a = 1
|
||||||
|
else:
|
||||||
|
a = "hello"
|
||||||
|
|
||||||
|
foo<CURSOR>(a)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.hover(), @r#"
|
||||||
|
(a: int) -> Unknown
|
||||||
|
(a: str) -> Unknown
|
||||||
|
---------------------------------------------
|
||||||
|
The first overload
|
||||||
|
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
(a: int) -> Unknown
|
||||||
|
(a: str) -> Unknown
|
||||||
|
```
|
||||||
|
---
|
||||||
|
```text
|
||||||
|
The first overload
|
||||||
|
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:19:13
|
||||||
|
|
|
||||||
|
17 | a = "hello"
|
||||||
|
18 |
|
||||||
|
19 | foo(a)
|
||||||
|
| ^^^- Cursor offset
|
||||||
|
| |
|
||||||
|
| source
|
||||||
|
|
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_module() {
|
fn hover_module() {
|
||||||
let mut test = cursor_test(
|
let mut test = cursor_test(
|
||||||
|
@ -1231,6 +1380,110 @@ mod tests {
|
||||||
assert_snapshot!(test.hover(), @"Hover provided no content");
|
assert_snapshot!(test.hover(), @"Hover provided no content");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_complex_type1() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
from typing import Callable, Any, List
|
||||||
|
def ab(x: int, y: Callable[[int, int], Any], z: List[int]) -> int: ...
|
||||||
|
|
||||||
|
a<CURSOR>b
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.hover(), @r"
|
||||||
|
def ab(
|
||||||
|
x: int,
|
||||||
|
y: (int, int, /) -> Any,
|
||||||
|
z: list[int]
|
||||||
|
) -> int
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
def ab(
|
||||||
|
x: int,
|
||||||
|
y: (int, int, /) -> Any,
|
||||||
|
z: list[int]
|
||||||
|
) -> int
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:5:9
|
||||||
|
|
|
||||||
|
3 | def ab(x: int, y: Callable[[int, int], Any], z: List[int]) -> int: ...
|
||||||
|
4 |
|
||||||
|
5 | ab
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_complex_type2() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
from typing import Callable, Tuple, Any
|
||||||
|
ab: Tuple[Any, int, Callable[[int, int], Any]] = ...
|
||||||
|
|
||||||
|
a<CURSOR>b
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.hover(), @r"
|
||||||
|
tuple[Any, int, (int, int, /) -> Any]
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
tuple[Any, int, (int, int, /) -> Any]
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:5:9
|
||||||
|
|
|
||||||
|
3 | ab: Tuple[Any, int, Callable[[int, int], Any]] = ...
|
||||||
|
4 |
|
||||||
|
5 | ab
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hover_complex_type3() {
|
||||||
|
let test = cursor_test(
|
||||||
|
r#"
|
||||||
|
from typing import Callable, Any
|
||||||
|
ab: Callable[[int, int], Any] | None = ...
|
||||||
|
|
||||||
|
a<CURSOR>b
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_snapshot!(test.hover(), @r"
|
||||||
|
((int, int, /) -> Any) | None
|
||||||
|
---------------------------------------------
|
||||||
|
```python
|
||||||
|
((int, int, /) -> Any) | None
|
||||||
|
```
|
||||||
|
---------------------------------------------
|
||||||
|
info[hover]: Hovered content is
|
||||||
|
--> main.py:5:9
|
||||||
|
|
|
||||||
|
3 | ab: Callable[[int, int], Any] | None = ...
|
||||||
|
4 |
|
||||||
|
5 | ab
|
||||||
|
| ^-
|
||||||
|
| ||
|
||||||
|
| |Cursor offset
|
||||||
|
| source
|
||||||
|
|
|
||||||
|
");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hover_docstring() {
|
fn hover_docstring() {
|
||||||
let test = cursor_test(
|
let test = cursor_test(
|
||||||
|
|
|
@ -19,6 +19,7 @@ pub use semantic_model::{
|
||||||
Completion, CompletionKind, HasDefinition, HasType, NameKind, SemanticModel,
|
Completion, CompletionKind, HasDefinition, HasType, NameKind, SemanticModel,
|
||||||
};
|
};
|
||||||
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
|
pub use site_packages::{PythonEnvironment, SitePackagesPaths, SysPrefixPathOrigin};
|
||||||
|
pub use types::DisplaySettings;
|
||||||
pub use types::ide_support::{
|
pub use types::ide_support::{
|
||||||
ImportAliasResolution, ResolvedDefinition, definitions_for_attribute,
|
ImportAliasResolution, ResolvedDefinition, definitions_for_attribute,
|
||||||
definitions_for_imported_symbol, definitions_for_name, map_stub_definition,
|
definitions_for_imported_symbol, definitions_for_name, map_stub_definition,
|
||||||
|
|
|
@ -41,6 +41,7 @@ use crate::types::class::{CodeGeneratorKind, Field};
|
||||||
pub(crate) use crate::types::class_base::ClassBase;
|
pub(crate) use crate::types::class_base::ClassBase;
|
||||||
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
use crate::types::context::{LintDiagnosticGuard, LintDiagnosticGuardBuilder};
|
||||||
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
use crate::types::diagnostic::{INVALID_AWAIT, INVALID_TYPE_FORM, UNSUPPORTED_BOOL_CONVERSION};
|
||||||
|
pub use crate::types::display::DisplaySettings;
|
||||||
use crate::types::enums::{enum_metadata, is_single_member_enum};
|
use crate::types::enums::{enum_metadata, is_single_member_enum};
|
||||||
use crate::types::function::{
|
use crate::types::function::{
|
||||||
DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
|
DataclassTransformerParams, FunctionDecorators, FunctionSpans, FunctionType, KnownFunction,
|
||||||
|
|
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue