-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[ty] introduce multiline pretty printer #19979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Diagnostic diff on typing conformance testsNo changes detected when running ty on typing conformance tests ✅ |
|
I've also added multiline rendering for overloads using a similar scheme to pyright (just render each signature on its own line, don't mention overloads at all). |
pydantic.Field now renders as: click here for long signature...(
default: EllipsisType,
*,
alias: str | None = PydanticUndefinedType,
alias_priority: int | None = PydanticUndefinedType,
validation_alias: str | AliasPath | AliasChoices | None = PydanticUndefinedType,
serialization_alias: str | None = PydanticUndefinedType,
title: str | None = PydanticUndefinedType,
field_title_generator: ((str, FieldInfo, /) -> str) | None = PydanticUndefinedType,
description: str | None = PydanticUndefinedType,
examples: list[Any] | None = PydanticUndefinedType,
exclude: bool | None = PydanticUndefinedType,
discriminator: str | Unknown | None = PydanticUndefinedType,
deprecated: @Todo(Support for `types.UnionType` instances in type expressions) | str | bool | None = PydanticUndefinedType,
json_schema_extra: @Todo(Support for `typing.TypeAlias`) | ((@Todo(Support for `typing.TypeAlias`), /) -> None) | None = PydanticUndefinedType,
frozen: bool | None = PydanticUndefinedType,
validate_default: bool | None = PydanticUndefinedType,
repr: bool = PydanticUndefinedType,
init: bool | None = PydanticUndefinedType,
init_var: bool | None = PydanticUndefinedType,
kw_only: bool | None = PydanticUndefinedType,
pattern: str | Pattern[str] | None = PydanticUndefinedType,
strict: bool | None = PydanticUndefinedType,
coerce_numbers_to_str: bool | None = PydanticUndefinedType,
gt: SupportsGt | None = PydanticUndefinedType,
ge: SupportsGe | None = PydanticUndefinedType,
lt: SupportsLt | None = PydanticUndefinedType,
le: SupportsLe | None = PydanticUndefinedType,
multiple_of: int | float | None = PydanticUndefinedType,
allow_inf_nan: bool | None = PydanticUndefinedType,
max_digits: int | None = PydanticUndefinedType,
decimal_places: int | None = PydanticUndefinedType,
min_length: int | None = PydanticUndefinedType,
max_length: int | None = PydanticUndefinedType,
union_mode: Literal["smart", "left_to_right"] = PydanticUndefinedType,
fail_fast: bool | None = PydanticUndefinedType,
**extra: @Todo(`Unpack[]` special form)
) -> Any
(
default: _T@Field,
*,
alias: str | None = PydanticUndefinedType,
alias_priority: int | None = PydanticUndefinedType,
validation_alias: str | AliasPath | AliasChoices | None = PydanticUndefinedType,
serialization_alias: str | None = PydanticUndefinedType,
title: str | None = PydanticUndefinedType,
field_title_generator: ((str, FieldInfo, /) -> str) | None = PydanticUndefinedType,
description: str | None = PydanticUndefinedType,
examples: list[Any] | None = PydanticUndefinedType,
exclude: bool | None = PydanticUndefinedType,
discriminator: str | Unknown | None = PydanticUndefinedType,
deprecated: @Todo(Support for `types.UnionType` instances in type expressions) | str | bool | None = PydanticUndefinedType,
json_schema_extra: @Todo(Support for `typing.TypeAlias`) | ((@Todo(Support for `typing.TypeAlias`), /) -> None) | None = PydanticUndefinedType,
frozen: bool | None = PydanticUndefinedType,
validate_default: bool | None = PydanticUndefinedType,
repr: bool = PydanticUndefinedType,
init: bool | None = PydanticUndefinedType,
init_var: bool | None = PydanticUndefinedType,
kw_only: bool | None = PydanticUndefinedType,
pattern: str | Pattern[str] | None = PydanticUndefinedType,
strict: bool | None = PydanticUndefinedType,
coerce_numbers_to_str: bool | None = PydanticUndefinedType,
gt: SupportsGt | None = PydanticUndefinedType,
ge: SupportsGe | None = PydanticUndefinedType,
lt: SupportsLt | None = PydanticUndefinedType,
le: SupportsLe | None = PydanticUndefinedType,
multiple_of: int | float | None = PydanticUndefinedType,
allow_inf_nan: bool | None = PydanticUndefinedType,
max_digits: int | None = PydanticUndefinedType,
decimal_places: int | None = PydanticUndefinedType,
min_length: int | None = PydanticUndefinedType,
max_length: int | None = PydanticUndefinedType,
union_mode: Literal["smart", "left_to_right"] = PydanticUndefinedType,
fail_fast: bool | None = PydanticUndefinedType,
**extra: @Todo(`Unpack[]` special form)
) -> _T@Field
(
*,
default_factory: (() -> _T@Field) | ((dict[str, Any], /) -> _T@Field),
alias: str | None = PydanticUndefinedType,
alias_priority: int | None = PydanticUndefinedType,
validation_alias: str | AliasPath | AliasChoices | None = PydanticUndefinedType,
serialization_alias: str | None = PydanticUndefinedType,
title: str | None = PydanticUndefinedType,
field_title_generator: ((str, FieldInfo, /) -> str) | None = PydanticUndefinedType,
description: str | None = PydanticUndefinedType,
examples: list[Any] | None = PydanticUndefinedType,
exclude: bool | None = PydanticUndefinedType,
discriminator: str | Unknown | None = PydanticUndefinedType,
deprecated: @Todo(Support for `types.UnionType` instances in type expressions) | str | bool | None = PydanticUndefinedType,
json_schema_extra: @Todo(Support for `typing.TypeAlias`) | ((@Todo(Support for `typing.TypeAlias`), /) -> None) | None = PydanticUndefinedType,
frozen: bool | None = PydanticUndefinedType,
validate_default: bool | None = PydanticUndefinedType,
repr: bool = PydanticUndefinedType,
init: bool | None = PydanticUndefinedType,
init_var: bool | None = PydanticUndefinedType,
kw_only: bool | None = PydanticUndefinedType,
pattern: str | Pattern[str] | None = PydanticUndefinedType,
strict: bool | None = PydanticUndefinedType,
coerce_numbers_to_str: bool | None = PydanticUndefinedType,
gt: SupportsGt | None = PydanticUndefinedType,
ge: SupportsGe | None = PydanticUndefinedType,
lt: SupportsLt | None = PydanticUndefinedType,
le: SupportsLe | None = PydanticUndefinedType,
multiple_of: int | float | None = PydanticUndefinedType,
allow_inf_nan: bool | None = PydanticUndefinedType,
max_digits: int | None = PydanticUndefinedType,
decimal_places: int | None = PydanticUndefinedType,
min_length: int | None = PydanticUndefinedType,
max_length: int | None = PydanticUndefinedType,
union_mode: Literal["smart", "left_to_right"] = PydanticUndefinedType,
fail_fast: bool | None = PydanticUndefinedType,
**extra: @Todo(`Unpack[]` special form)
) -> _T@Field
(
*,
alias: str | None = PydanticUndefinedType,
alias_priority: int | None = PydanticUndefinedType,
validation_alias: str | AliasPath | AliasChoices | None = PydanticUndefinedType,
serialization_alias: str | None = PydanticUndefinedType,
title: str | None = PydanticUndefinedType,
field_title_generator: ((str, FieldInfo, /) -> str) | None = PydanticUndefinedType,
description: str | None = PydanticUndefinedType,
examples: list[Any] | None = PydanticUndefinedType,
exclude: bool | None = PydanticUndefinedType,
discriminator: str | Unknown | None = PydanticUndefinedType,
deprecated: @Todo(Support for `types.UnionType` instances in type expressions) | str | bool | None = PydanticUndefinedType,
json_schema_extra: @Todo(Support for `typing.TypeAlias`) | ((@Todo(Support for `typing.TypeAlias`), /) -> None) | None = PydanticUndefinedType,
frozen: bool | None = PydanticUndefinedType,
validate_default: bool | None = PydanticUndefinedType,
repr: bool = PydanticUndefinedType,
init: bool | None = PydanticUndefinedType,
init_var: bool | None = PydanticUndefinedType,
kw_only: bool | None = PydanticUndefinedType,
pattern: str | Pattern[str] | None = PydanticUndefinedType,
strict: bool | None = PydanticUndefinedType,
coerce_numbers_to_str: bool | None = PydanticUndefinedType,
gt: SupportsGt | None = PydanticUndefinedType,
ge: SupportsGe | None = PydanticUndefinedType,
lt: SupportsLt | None = PydanticUndefinedType,
le: SupportsLe | None = PydanticUndefinedType,
multiple_of: int | float | None = PydanticUndefinedType,
allow_inf_nan: bool | None = PydanticUndefinedType,
max_digits: int | None = PydanticUndefinedType,
decimal_places: int | None = PydanticUndefinedType,
min_length: int | None = PydanticUndefinedType,
max_length: int | None = PydanticUndefinedType,
union_mode: Literal["smart", "left_to_right"] = PydanticUndefinedType,
fail_fast: bool | None = PydanticUndefinedType,
**extra: @Todo(`Unpack[]` special form)
) -> Any
Which is about on-par with pyright (except for all the TODOs) |
I've also now heavily restricted the places where multiline printing recursively propagates -- in particular it seems pylance basically only ever multi-line prints a function type if it's the top-level type. As such most types now apply |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
def my_func( | ||
a, | ||
b | ||
) -> Unknown |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like this is a case where keeping it on one line would be preferable.
@@ -600,9 +762,12 @@ impl DisplaySignature<'_> { | |||
|
|||
/// Internal method to write signature with the signature writer | |||
fn write_signature(&self, writer: &mut SignatureWriter) -> fmt::Result { | |||
let multiline = self.settings.multiline && self.parameters.len() > 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this self.parameters.len() > 1
heuristic could probably use some improvement. Ideally it would be based on "how many characters is the rendered display"? That might not be too hard to do, if we approach it as building up a vector of segments, summing their length, and then at the end joining them with the right separator?
Ah ok! Well that suggests that it's fine to go with this heuristic for now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine with this simple heuristic for now. I think it would be good to add some tests that demonstrate how the rendering looks when nesting multiple complex types.
This code looks very similar to what ruff_formatter
supports and we could use it to e.g. avoid breaking a simple f(a, b)
signature if it fits into some line length that we pick.
What I don't know is if the formatter is fast enough for the case where we emit many diagnostics, but it probably is.
Here's some docs on the formatter supported IR elements: https://github.com/astral-sh/ruff/blob/7dfde3b929c70b5f5fb9933ef09b8005717a8d85/crates/ruff_formatter/src/builders.rs#L1-L0
@@ -611,12 +776,13 @@ impl DisplaySignature<'_> { | |||
let mut star_added = false; | |||
let mut needs_slash = false; | |||
let mut first = true; | |||
let arg_separator = if multiline { ",\n " } else { ", " }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the indent work as expected for nested callable types (where a parameter type is a callable type itself that needs to break over multiple lines)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is avoided by the gratuitous uses of settings.singleline()
preventing nested types from being line-wrapped almost anywhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I'll add tests to show that)
Just in case it isn't clear from my previous comment. I don't think we need to explore using ruff's formatter now. But we may want to if we want to do more fancy rendering |
5219d19
to
40db3b0
Compare
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 astral-sh/ty#1000