mirror of https://github.com/astral-sh/ruff
[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
|
|
@ -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: 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
|
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: 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
|
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: 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
|
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
|
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: 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
|
info: rule `no-matching-overload` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -176,7 +176,7 @@ info: Function defined here
|
||||||
8 | return 0
|
8 | return 0
|
||||||
|
|
|
|
||||||
info: Union variant `def f2(name: str) -> int` is incompatible with this call site
|
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
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -200,7 +200,7 @@ info: Type variable defined here
|
||||||
14 | return 0
|
14 | return 0
|
||||||
|
|
|
|
||||||
info: Union variant `def f4[T](x: T@f4) -> int` is incompatible with this call site
|
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
|
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: Non-matching overloads for function `f5`:
|
||||||
info: () -> None
|
info: () -> None
|
||||||
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
|
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
|
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: 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
|
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
|
/// Class names that should be displayed fully qualified
|
||||||
/// (e.g., `module.ClassName` instead of just `ClassName`)
|
/// (e.g., `module.ClassName` instead of just `ClassName`)
|
||||||
pub qualified: Rc<FxHashSet<&'db str>>,
|
pub qualified: Rc<FxHashSet<&'db str>>,
|
||||||
|
/// Whether long unions are displayed in full
|
||||||
|
pub preserve_full_unions: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'db> DisplaySettings<'db> {
|
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]
|
#[must_use]
|
||||||
pub fn from_possibly_ambiguous_type_pair(
|
pub fn from_possibly_ambiguous_type_pair(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
|
|
@ -1265,6 +1283,9 @@ struct DisplayUnionType<'db> {
|
||||||
settings: DisplaySettings<'db>,
|
settings: DisplaySettings<'db>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_DISPLAYED_UNION_ITEMS: usize = 5;
|
||||||
|
const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3;
|
||||||
|
|
||||||
impl Display for DisplayUnionType<'_> {
|
impl Display for DisplayUnionType<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
fn is_condensable(ty: Type<'_>) -> bool {
|
fn is_condensable(ty: Type<'_>) -> bool {
|
||||||
|
|
@ -1286,12 +1307,35 @@ impl Display for DisplayUnionType<'_> {
|
||||||
.filter(|element| is_condensable(*element))
|
.filter(|element| is_condensable(*element))
|
||||||
.collect::<Vec<_>>();
|
.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 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 condensed_types = Some(condensed_types);
|
||||||
|
let mut displayed_entries = 0usize;
|
||||||
|
|
||||||
for element in elements {
|
for element in elements {
|
||||||
|
if displayed_entries >= display_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if is_condensable(*element) {
|
if is_condensable(*element) {
|
||||||
if let Some(condensed_types) = condensed_types.take() {
|
if let Some(condensed_types) = condensed_types.take() {
|
||||||
|
displayed_entries += 1;
|
||||||
join.entry(&DisplayLiteralGroup {
|
join.entry(&DisplayLiteralGroup {
|
||||||
literals: condensed_types,
|
literals: condensed_types,
|
||||||
db: self.db,
|
db: self.db,
|
||||||
|
|
@ -1299,6 +1343,7 @@ impl Display for DisplayUnionType<'_> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
displayed_entries += 1;
|
||||||
join.entry(&DisplayMaybeParenthesizedType {
|
join.entry(&DisplayMaybeParenthesizedType {
|
||||||
ty: *element,
|
ty: *element,
|
||||||
db: self.db,
|
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()?;
|
join.finish()?;
|
||||||
|
|
||||||
Ok(())
|
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> {
|
struct DisplayLiteralGroup<'db> {
|
||||||
literals: Vec<Type<'db>>,
|
literals: Vec<Type<'db>>,
|
||||||
db: &'db dyn 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_bad_argument_to_get_protocol_members, report_bad_argument_to_protocol_interface,
|
||||||
report_runtime_check_against_non_runtime_checkable_protocol,
|
report_runtime_check_against_non_runtime_checkable_protocol,
|
||||||
};
|
};
|
||||||
|
use crate::types::display::DisplaySettings;
|
||||||
use crate::types::generics::GenericContext;
|
use crate::types::generics::GenericContext;
|
||||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||||
use crate::types::signatures::{CallableSignature, Signature};
|
use crate::types::signatures::{CallableSignature, Signature};
|
||||||
|
|
@ -1386,10 +1387,11 @@ impl KnownFunction {
|
||||||
{
|
{
|
||||||
let mut diag = builder.into_diagnostic("Revealed type");
|
let mut diag = builder.into_diagnostic("Revealed type");
|
||||||
let span = context.span(&call_expression.arguments.args[0]);
|
let span = context.span(&call_expression.arguments.args[0]);
|
||||||
diag.annotate(
|
diag.annotate(Annotation::primary(span).message(format_args!(
|
||||||
Annotation::primary(span)
|
"`{}`",
|
||||||
.message(format_args!("`{}`", revealed_type.display(db))),
|
revealed_type
|
||||||
);
|
.display_with(db, DisplaySettings::default().preserve_long_unions())
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue