Skip to content

Commit f95eb90

Browse files
[ty] Truncate type display for long unions in some situations (#20730)
## Summary Fixes [astral-sh/ty#1307](astral-sh/ty#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
1 parent 1f1542d commit f95eb90

File tree

3 files changed

+83
-12
lines changed

3 files changed

+83
-12
lines changed

crates/ty_python_semantic/resources/mdtest/snapshots/union_call.md_-_Calling_a_union_of_f…_-_Try_to_cover_all_pos…_-_Cover_non-keyword_re…_(707b284610419a54).snap

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ error[call-non-callable]: Object of type `Literal[5]` is not callable
8686
| ^^^^
8787
|
8888
info: Union variant `Literal[5]` is incompatible with this call site
89-
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`
89+
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`
9090
info: rule `call-non-callable` is enabled by default
9191

9292
```
@@ -101,7 +101,7 @@ error[call-non-callable]: Object of type `PossiblyNotCallable` is not callable (
101101
| ^^^^
102102
|
103103
info: Union variant `PossiblyNotCallable` is incompatible with this call site
104-
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`
104+
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`
105105
info: rule `call-non-callable` is enabled by default
106106

107107
```
@@ -116,7 +116,7 @@ error[missing-argument]: No argument provided for required parameter `b` of func
116116
| ^^^^
117117
|
118118
info: Union variant `def f3(a: int, b: int) -> int` is incompatible with this call site
119-
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`
119+
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`
120120
info: rule `missing-argument` is enabled by default
121121

122122
```
@@ -152,7 +152,7 @@ info: Overload implementation defined here
152152
28 | return x + y if x and y else None
153153
|
154154
info: Union variant `Overload[() -> None, (x: str, y: str) -> str]` is incompatible with this call site
155-
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`
155+
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`
156156
info: rule `no-matching-overload` is enabled by default
157157

158158
```
@@ -176,7 +176,7 @@ info: Function defined here
176176
8 | return 0
177177
|
178178
info: Union variant `def f2(name: str) -> int` is incompatible with this call site
179-
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`
179+
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`
180180
info: rule `invalid-argument-type` is enabled by default
181181

182182
```
@@ -200,7 +200,7 @@ info: Type variable defined here
200200
14 | return 0
201201
|
202202
info: Union variant `def f4[T](x: T@f4) -> int` is incompatible with this call site
203-
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`
203+
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`
204204
info: rule `invalid-argument-type` is enabled by default
205205

206206
```
@@ -227,7 +227,7 @@ info: Matching overload defined here
227227
info: Non-matching overloads for function `f5`:
228228
info: () -> None
229229
info: Union variant `Overload[() -> None, (x: str) -> str]` is incompatible with this call site
230-
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`
230+
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`
231231
info: rule `invalid-argument-type` is enabled by default
232232

233233
```
@@ -242,7 +242,7 @@ error[too-many-positional-arguments]: Too many positional arguments to function
242242
| ^
243243
|
244244
info: Union variant `def f1() -> int` is incompatible with this call site
245-
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`
245+
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`
246246
info: rule `too-many-positional-arguments` is enabled by default
247247

248248
```

crates/ty_python_semantic/src/types/display.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pub struct DisplaySettings<'db> {
3535
/// Class names that should be displayed fully qualified
3636
/// (e.g., `module.ClassName` instead of just `ClassName`)
3737
pub qualified: Rc<FxHashSet<&'db str>>,
38+
/// Whether long unions are displayed in full
39+
pub preserve_full_unions: bool,
3840
}
3941

4042
impl<'db> DisplaySettings<'db> {
@@ -54,6 +56,22 @@ impl<'db> DisplaySettings<'db> {
5456
}
5557
}
5658

59+
#[must_use]
60+
pub fn truncate_long_unions(self) -> Self {
61+
Self {
62+
preserve_full_unions: false,
63+
..self
64+
}
65+
}
66+
67+
#[must_use]
68+
pub fn preserve_long_unions(self) -> Self {
69+
Self {
70+
preserve_full_unions: true,
71+
..self
72+
}
73+
}
74+
5775
#[must_use]
5876
pub fn from_possibly_ambiguous_type_pair(
5977
db: &'db dyn Db,
@@ -1265,6 +1283,9 @@ struct DisplayUnionType<'db> {
12651283
settings: DisplaySettings<'db>,
12661284
}
12671285

1286+
const MAX_DISPLAYED_UNION_ITEMS: usize = 5;
1287+
const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3;
1288+
12681289
impl Display for DisplayUnionType<'_> {
12691290
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
12701291
fn is_condensable(ty: Type<'_>) -> bool {
@@ -1286,19 +1307,43 @@ impl Display for DisplayUnionType<'_> {
12861307
.filter(|element| is_condensable(*element))
12871308
.collect::<Vec<_>>();
12881309

1310+
let total_entries =
1311+
usize::from(!condensed_types.is_empty()) + elements.len() - condensed_types.len();
1312+
1313+
assert_ne!(total_entries, 0);
1314+
12891315
let mut join = f.join(" | ");
12901316

1317+
let display_limit = if self.settings.preserve_full_unions {
1318+
total_entries
1319+
} else {
1320+
let limit = if total_entries > MAX_DISPLAYED_UNION_ITEMS {
1321+
MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED
1322+
} else {
1323+
MAX_DISPLAYED_UNION_ITEMS
1324+
};
1325+
limit.min(total_entries)
1326+
};
1327+
12911328
let mut condensed_types = Some(condensed_types);
1329+
let mut displayed_entries = 0usize;
1330+
12921331
for element in elements {
1332+
if displayed_entries >= display_limit {
1333+
break;
1334+
}
1335+
12931336
if is_condensable(*element) {
12941337
if let Some(condensed_types) = condensed_types.take() {
1338+
displayed_entries += 1;
12951339
join.entry(&DisplayLiteralGroup {
12961340
literals: condensed_types,
12971341
db: self.db,
12981342
settings: self.settings.singleline(),
12991343
});
13001344
}
13011345
} else {
1346+
displayed_entries += 1;
13021347
join.entry(&DisplayMaybeParenthesizedType {
13031348
ty: *element,
13041349
db: self.db,
@@ -1307,6 +1352,15 @@ impl Display for DisplayUnionType<'_> {
13071352
}
13081353
}
13091354

1355+
if !self.settings.preserve_full_unions {
1356+
let omitted_entries = total_entries.saturating_sub(displayed_entries);
1357+
if omitted_entries > 0 {
1358+
join.entry(&DisplayUnionOmitted {
1359+
count: omitted_entries,
1360+
});
1361+
}
1362+
}
1363+
13101364
join.finish()?;
13111365

13121366
Ok(())
@@ -1319,6 +1373,21 @@ impl fmt::Debug for DisplayUnionType<'_> {
13191373
}
13201374
}
13211375

1376+
struct DisplayUnionOmitted {
1377+
count: usize,
1378+
}
1379+
1380+
impl Display for DisplayUnionOmitted {
1381+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1382+
let plural = if self.count == 1 {
1383+
"element"
1384+
} else {
1385+
"elements"
1386+
};
1387+
write!(f, "... omitted {} union {}", self.count, plural)
1388+
}
1389+
}
1390+
13221391
struct DisplayLiteralGroup<'db> {
13231392
literals: Vec<Type<'db>>,
13241393
db: &'db dyn Db,

crates/ty_python_semantic/src/types/function.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ use crate::types::diagnostic::{
7272
report_bad_argument_to_get_protocol_members, report_bad_argument_to_protocol_interface,
7373
report_runtime_check_against_non_runtime_checkable_protocol,
7474
};
75+
use crate::types::display::DisplaySettings;
7576
use crate::types::generics::GenericContext;
7677
use crate::types::narrow::ClassInfoConstraintFunction;
7778
use crate::types::signatures::{CallableSignature, Signature};
@@ -1386,10 +1387,11 @@ impl KnownFunction {
13861387
{
13871388
let mut diag = builder.into_diagnostic("Revealed type");
13881389
let span = context.span(&call_expression.arguments.args[0]);
1389-
diag.annotate(
1390-
Annotation::primary(span)
1391-
.message(format_args!("`{}`", revealed_type.display(db))),
1392-
);
1390+
diag.annotate(Annotation::primary(span).message(format_args!(
1391+
"`{}`",
1392+
revealed_type
1393+
.display_with(db, DisplaySettings::default().preserve_long_unions())
1394+
)));
13931395
}
13941396
}
13951397

0 commit comments

Comments
 (0)