Skip to content

Commit fc3b341

Browse files
[ty] Truncate Literal type display in some situations (#20928)
1 parent baaa8da commit fc3b341

File tree

3 files changed

+158
-37
lines changed

3 files changed

+158
-37
lines changed

crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,27 @@ def _(n: int):
138138
# error: [unknown-argument]
139139
y = f("foo", name="bar", unknown="quux")
140140
```
141+
142+
### Truncation for long unions and literals
143+
144+
This test demonstrates a call where the expected type is a large mixed union. The diagnostic must
145+
therefore truncate the long expected union type to avoid overwhelming output.
146+
147+
```py
148+
from typing import Literal, Union
149+
150+
class A: ...
151+
class B: ...
152+
class C: ...
153+
class D: ...
154+
class E: ...
155+
class F: ...
156+
157+
def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
158+
return 0
159+
160+
def _(n: int):
161+
x = n
162+
# error: [invalid-argument-type]
163+
f1(x)
164+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
---
2+
source: crates/ty_test/src/lib.rs
3+
assertion_line: 427
4+
expression: snapshot
5+
---
6+
---
7+
mdtest name: union_call.md - Calling a union of function types - Try to cover all possible reasons - Truncation for long unions and literals
8+
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md
9+
---
10+
11+
# Python source files
12+
13+
## mdtest_snippet.py
14+
15+
```
16+
1 | from typing import Literal, Union
17+
2 |
18+
3 | class A: ...
19+
4 | class B: ...
20+
5 | class C: ...
21+
6 | class D: ...
22+
7 | class E: ...
23+
8 | class F: ...
24+
9 |
25+
10 | def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
26+
11 | return 0
27+
12 |
28+
13 | def _(n: int):
29+
14 | x = n
30+
15 | # error: [invalid-argument-type]
31+
16 | f1(x)
32+
```
33+
34+
# Diagnostics
35+
36+
```
37+
error[invalid-argument-type]: Argument to function `f1` is incorrect
38+
--> src/mdtest_snippet.py:16:8
39+
|
40+
14 | x = n
41+
15 | # error: [invalid-argument-type]
42+
16 | f1(x)
43+
| ^ Expected `Literal[1, 2, 3, 4, 5, ... omitted 3 literals] | A | B | ... omitted 4 union elements`, found `int`
44+
|
45+
info: Function defined here
46+
--> src/mdtest_snippet.py:10:5
47+
|
48+
8 | class F: ...
49+
9 |
50+
10 | def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
51+
| ^^ ----------------------------------------------------------- Parameter declared here
52+
11 | return 0
53+
|
54+
info: rule `invalid-argument-type` is enabled by default
55+
56+
```

crates/ty_python_semantic/src/types/display.rs

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub struct DisplaySettings<'db> {
3838
/// Class names that should be displayed fully qualified
3939
/// (e.g., `module.ClassName` instead of just `ClassName`)
4040
pub qualified: Rc<FxHashMap<&'db str, QualificationLevel>>,
41-
/// Whether long unions are displayed in full
41+
/// Whether long unions and literals are displayed in full
4242
pub preserve_full_unions: bool,
4343
}
4444

@@ -1328,6 +1328,44 @@ impl Display for DisplayParameter<'_> {
13281328
}
13291329
}
13301330

1331+
#[derive(Debug, Copy, Clone)]
1332+
struct TruncationPolicy {
1333+
max: usize,
1334+
max_when_elided: usize,
1335+
}
1336+
1337+
impl TruncationPolicy {
1338+
fn display_limit(self, total: usize, preserve_full: bool) -> usize {
1339+
if preserve_full {
1340+
return total;
1341+
}
1342+
let limit = if total > self.max {
1343+
self.max_when_elided
1344+
} else {
1345+
self.max
1346+
};
1347+
limit.min(total)
1348+
}
1349+
}
1350+
1351+
#[derive(Debug)]
1352+
struct DisplayOmitted {
1353+
count: usize,
1354+
singular: &'static str,
1355+
plural: &'static str,
1356+
}
1357+
1358+
impl Display for DisplayOmitted {
1359+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1360+
let noun = if self.count == 1 {
1361+
self.singular
1362+
} else {
1363+
self.plural
1364+
};
1365+
write!(f, "... omitted {} {}", self.count, noun)
1366+
}
1367+
}
1368+
13311369
impl<'db> UnionType<'db> {
13321370
fn display_with(
13331371
&'db self,
@@ -1348,8 +1386,10 @@ struct DisplayUnionType<'db> {
13481386
settings: DisplaySettings<'db>,
13491387
}
13501388

1351-
const MAX_DISPLAYED_UNION_ITEMS: usize = 5;
1352-
const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3;
1389+
const UNION_POLICY: TruncationPolicy = TruncationPolicy {
1390+
max: 5,
1391+
max_when_elided: 3,
1392+
};
13531393

13541394
impl Display for DisplayUnionType<'_> {
13551395
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
@@ -1379,16 +1419,8 @@ impl Display for DisplayUnionType<'_> {
13791419

13801420
let mut join = f.join(" | ");
13811421

1382-
let display_limit = if self.settings.preserve_full_unions {
1383-
total_entries
1384-
} else {
1385-
let limit = if total_entries > MAX_DISPLAYED_UNION_ITEMS {
1386-
MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED
1387-
} else {
1388-
MAX_DISPLAYED_UNION_ITEMS
1389-
};
1390-
limit.min(total_entries)
1391-
};
1422+
let display_limit =
1423+
UNION_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
13921424

13931425
let mut condensed_types = Some(condensed_types);
13941426
let mut displayed_entries = 0usize;
@@ -1420,8 +1452,10 @@ impl Display for DisplayUnionType<'_> {
14201452
if !self.settings.preserve_full_unions {
14211453
let omitted_entries = total_entries.saturating_sub(displayed_entries);
14221454
if omitted_entries > 0 {
1423-
join.entry(&DisplayUnionOmitted {
1455+
join.entry(&DisplayOmitted {
14241456
count: omitted_entries,
1457+
singular: "union element",
1458+
plural: "union elements",
14251459
});
14261460
}
14271461
}
@@ -1437,38 +1471,45 @@ impl fmt::Debug for DisplayUnionType<'_> {
14371471
Display::fmt(self, f)
14381472
}
14391473
}
1440-
1441-
struct DisplayUnionOmitted {
1442-
count: usize,
1443-
}
1444-
1445-
impl Display for DisplayUnionOmitted {
1446-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1447-
let plural = if self.count == 1 {
1448-
"element"
1449-
} else {
1450-
"elements"
1451-
};
1452-
write!(f, "... omitted {} union {}", self.count, plural)
1453-
}
1454-
}
1455-
14561474
struct DisplayLiteralGroup<'db> {
14571475
literals: Vec<Type<'db>>,
14581476
db: &'db dyn Db,
14591477
settings: DisplaySettings<'db>,
14601478
}
14611479

1480+
const LITERAL_POLICY: TruncationPolicy = TruncationPolicy {
1481+
max: 7,
1482+
max_when_elided: 5,
1483+
};
1484+
14621485
impl Display for DisplayLiteralGroup<'_> {
14631486
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
14641487
f.write_str("Literal[")?;
1465-
f.join(", ")
1466-
.entries(
1467-
self.literals
1468-
.iter()
1469-
.map(|ty| ty.representation(self.db, self.settings.singleline())),
1470-
)
1471-
.finish()?;
1488+
1489+
let total_entries = self.literals.len();
1490+
1491+
let display_limit =
1492+
LITERAL_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
1493+
1494+
let mut join = f.join(", ");
1495+
1496+
for lit in self.literals.iter().take(display_limit) {
1497+
let rep = lit.representation(self.db, self.settings.singleline());
1498+
join.entry(&rep);
1499+
}
1500+
1501+
if !self.settings.preserve_full_unions {
1502+
let omitted_entries = total_entries.saturating_sub(display_limit);
1503+
if omitted_entries > 0 {
1504+
join.entry(&DisplayOmitted {
1505+
count: omitted_entries,
1506+
singular: "literal",
1507+
plural: "literals",
1508+
});
1509+
}
1510+
}
1511+
1512+
join.finish()?;
14721513
f.write_str("]")
14731514
}
14741515
}

0 commit comments

Comments
 (0)