mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-17 13:57:25 +00:00
[ty] Truncate type display for long unions in some situations (#20730)
## Summary Fixes [astral-sh/ty#1307](https://github.com/astral-sh/ty/issues/1307) Unions with length <= 5 are unaffected to minimize test churn Unions with length > 5 will only display the first 3 elements + "... omitted x union elements" Here "length" is defined as the number of elements after condensation to literals Edit: we no longer truncate in revel case. Before: > info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable` After: > info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements` The below comparisons are outdated, but left here as a reference. Before: ```reveal_type(x) # revealed: Literal[1, 2] | A | B | C | D | E | F | G``` ```reveal_type(x) # revealed: Result1A | Result1B | Result2A | Result2B | Result3 | Result4``` After: ```reveal_type(x) # revealed: Literal[1, 2] | A | B | ... omitted 5 union elements``` ```reveal_type(x) # revealed: Result1A | Result1B | Result2A | ... omitted 3 union elements``` This formatting is consistent with `crates/ty_python_semantic/src/types/call/bind.rs` line 2992 ## Test Plan Cosmetic only, covered and verified by changes in mdtest
This commit is contained in:
parent
1f1542db51
commit
f95eb90951
3 changed files with 83 additions and 12 deletions
|
@ -86,7 +86,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable
|
|||
| ^^^^
|
||||
|
|
||||
info: Union variant `Literal[5]` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `call-non-callable` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -101,7 +101,7 @@ error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (
|
|||
| ^^^^
|
||||
|
|
||||
info: Union variant `PossiblyNotCallable` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `call-non-callable` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -116,7 +116,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func
|
|||
| ^^^^
|
||||
|
|
||||
info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `missing-argument` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -152,7 +152,7 @@ info: Overload implementation defined here
|
|||
28 | return x + y if x and y else None
|
||||
|
|
||||
info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `no-matching-overload` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -176,7 +176,7 @@ info: Function defined here
|
|||
8 | return 0
|
||||
|
|
||||
info: Union variant `def f2(name: str) -> int` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -200,7 +200,7 @@ info: Type variable defined here
|
|||
14 | return 0
|
||||
|
|
||||
info: Union variant `def f4[T](x: T@f4) -> int` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -227,7 +227,7 @@ info: Matching overload defined here
|
|||
info: Non-matching overloads for function `f5`:
|
||||
info: () -> None
|
||||
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `invalid-argument-type` is enabled by default
|
||||
|
||||
```
|
||||
|
@ -242,7 +242,7 @@ error[too-many-positional-arguments]: Too many positional arguments to function
|
|||
| ^
|
||||
|
|
||||
info: Union variant `def f1() -> int` is incompatible with this call site
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | (def f4[T](x: T@f4) -> int) | Literal[5] | (Overload[() -> None, (x: str) -> str]) | (Overload[() -> None, (x: str, y: str) -> str]) | PossiblyNotCallable`
|
||||
info: Attempted to call union type `(def f1() -> int) | (def f2(name: str) -> int) | (def f3(a: int, b: int) -> int) | ... omitted 5 union elements`
|
||||
info: rule `too-many-positional-arguments` is enabled by default
|
||||
|
||||
```
|
||||
|
|
|
@ -35,6 +35,8 @@ pub struct DisplaySettings<'db> {
|
|||
/// Class names that should be displayed fully qualified
|
||||
/// (e.g., `module.ClassName` instead of just `ClassName`)
|
||||
pub qualified: Rc<FxHashSet<&'db str>>,
|
||||
/// Whether long unions are displayed in full
|
||||
pub preserve_full_unions: bool,
|
||||
}
|
||||
|
||||
impl<'db> DisplaySettings<'db> {
|
||||
|
@ -54,6 +56,22 @@ impl<'db> DisplaySettings<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn truncate_long_unions(self) -> Self {
|
||||
Self {
|
||||
preserve_full_unions: false,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn preserve_long_unions(self) -> Self {
|
||||
Self {
|
||||
preserve_full_unions: true,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_possibly_ambiguous_type_pair(
|
||||
db: &'db dyn Db,
|
||||
|
@ -1265,6 +1283,9 @@ struct DisplayUnionType<'db> {
|
|||
settings: DisplaySettings<'db>,
|
||||
}
|
||||
|
||||
const MAX_DISPLAYED_UNION_ITEMS: usize = 5;
|
||||
const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3;
|
||||
|
||||
impl Display for DisplayUnionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
fn is_condensable(ty: Type<'_>) -> bool {
|
||||
|
@ -1286,12 +1307,35 @@ impl Display for DisplayUnionType<'_> {
|
|||
.filter(|element| is_condensable(*element))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let total_entries =
|
||||
usize::from(!condensed_types.is_empty()) + elements.len() - condensed_types.len();
|
||||
|
||||
assert_ne!(total_entries, 0);
|
||||
|
||||
let mut join = f.join(" | ");
|
||||
|
||||
let display_limit = if self.settings.preserve_full_unions {
|
||||
total_entries
|
||||
} else {
|
||||
let limit = if total_entries > MAX_DISPLAYED_UNION_ITEMS {
|
||||
MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED
|
||||
} else {
|
||||
MAX_DISPLAYED_UNION_ITEMS
|
||||
};
|
||||
limit.min(total_entries)
|
||||
};
|
||||
|
||||
let mut condensed_types = Some(condensed_types);
|
||||
let mut displayed_entries = 0usize;
|
||||
|
||||
for element in elements {
|
||||
if displayed_entries >= display_limit {
|
||||
break;
|
||||
}
|
||||
|
||||
if is_condensable(*element) {
|
||||
if let Some(condensed_types) = condensed_types.take() {
|
||||
displayed_entries += 1;
|
||||
join.entry(&DisplayLiteralGroup {
|
||||
literals: condensed_types,
|
||||
db: self.db,
|
||||
|
@ -1299,6 +1343,7 @@ impl Display for DisplayUnionType<'_> {
|
|||
});
|
||||
}
|
||||
} else {
|
||||
displayed_entries += 1;
|
||||
join.entry(&DisplayMaybeParenthesizedType {
|
||||
ty: *element,
|
||||
db: self.db,
|
||||
|
@ -1307,6 +1352,15 @@ impl Display for DisplayUnionType<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
if !self.settings.preserve_full_unions {
|
||||
let omitted_entries = total_entries.saturating_sub(displayed_entries);
|
||||
if omitted_entries > 0 {
|
||||
join.entry(&DisplayUnionOmitted {
|
||||
count: omitted_entries,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
join.finish()?;
|
||||
|
||||
Ok(())
|
||||
|
@ -1319,6 +1373,21 @@ impl fmt::Debug for DisplayUnionType<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
struct DisplayUnionOmitted {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Display for DisplayUnionOmitted {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let plural = if self.count == 1 {
|
||||
"element"
|
||||
} else {
|
||||
"elements"
|
||||
};
|
||||
write!(f, "... omitted {} union {}", self.count, plural)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayLiteralGroup<'db> {
|
||||
literals: Vec<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
|
|
|
@ -72,6 +72,7 @@ use crate::types::diagnostic::{
|
|||
report_bad_argument_to_get_protocol_members, report_bad_argument_to_protocol_interface,
|
||||
report_runtime_check_against_non_runtime_checkable_protocol,
|
||||
};
|
||||
use crate::types::display::DisplaySettings;
|
||||
use crate::types::generics::GenericContext;
|
||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||
use crate::types::signatures::{CallableSignature, Signature};
|
||||
|
@ -1386,10 +1387,11 @@ impl KnownFunction {
|
|||
{
|
||||
let mut diag = builder.into_diagnostic("Revealed type");
|
||||
let span = context.span(&call_expression.arguments.args[0]);
|
||||
diag.annotate(
|
||||
Annotation::primary(span)
|
||||
.message(format_args!("`{}`", revealed_type.display(db))),
|
||||
);
|
||||
diag.annotate(Annotation::primary(span).message(format_args!(
|
||||
"`{}`",
|
||||
revealed_type
|
||||
.display_with(db, DisplaySettings::default().preserve_long_unions())
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue