Skip to content

Commit d4c6ac0

Browse files
[ty] Truncate Literal type display in some situations
1 parent 1ade4f2 commit d4c6ac0

File tree

3 files changed

+157
-37
lines changed

3 files changed

+157
-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: 77 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

@@ -1322,6 +1322,43 @@ impl Display for DisplayParameter<'_> {
13221322
}
13231323
}
13241324

1325+
#[derive(Copy, Clone)]
1326+
struct TruncationPolicy {
1327+
max: usize,
1328+
max_when_elided: usize,
1329+
}
1330+
1331+
impl TruncationPolicy {
1332+
fn display_limit(self, total: usize, preserve_full: bool) -> usize {
1333+
if preserve_full {
1334+
return total;
1335+
}
1336+
let limit = if total > self.max {
1337+
self.max_when_elided
1338+
} else {
1339+
self.max
1340+
};
1341+
limit.min(total)
1342+
}
1343+
}
1344+
1345+
struct DisplayOmitted {
1346+
count: usize,
1347+
singular: &'static str,
1348+
plural: &'static str,
1349+
}
1350+
1351+
impl Display for DisplayOmitted {
1352+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1353+
let noun = if self.count == 1 {
1354+
self.singular
1355+
} else {
1356+
self.plural
1357+
};
1358+
write!(f, "... omitted {} {}", self.count, noun)
1359+
}
1360+
}
1361+
13251362
impl<'db> UnionType<'db> {
13261363
fn display_with(
13271364
&'db self,
@@ -1342,8 +1379,10 @@ struct DisplayUnionType<'db> {
13421379
settings: DisplaySettings<'db>,
13431380
}
13441381

1345-
const MAX_DISPLAYED_UNION_ITEMS: usize = 5;
1346-
const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3;
1382+
const UNION_POLICY: TruncationPolicy = TruncationPolicy {
1383+
max: 5,
1384+
max_when_elided: 3,
1385+
};
13471386

13481387
impl Display for DisplayUnionType<'_> {
13491388
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
@@ -1373,16 +1412,8 @@ impl Display for DisplayUnionType<'_> {
13731412

13741413
let mut join = f.join(" | ");
13751414

1376-
let display_limit = if self.settings.preserve_full_unions {
1377-
total_entries
1378-
} else {
1379-
let limit = if total_entries > MAX_DISPLAYED_UNION_ITEMS {
1380-
MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED
1381-
} else {
1382-
MAX_DISPLAYED_UNION_ITEMS
1383-
};
1384-
limit.min(total_entries)
1385-
};
1415+
let display_limit =
1416+
UNION_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
13861417

13871418
let mut condensed_types = Some(condensed_types);
13881419
let mut displayed_entries = 0usize;
@@ -1414,8 +1445,10 @@ impl Display for DisplayUnionType<'_> {
14141445
if !self.settings.preserve_full_unions {
14151446
let omitted_entries = total_entries.saturating_sub(displayed_entries);
14161447
if omitted_entries > 0 {
1417-
join.entry(&DisplayUnionOmitted {
1448+
join.entry(&DisplayOmitted {
14181449
count: omitted_entries,
1450+
singular: "union element",
1451+
plural: "union elements",
14191452
});
14201453
}
14211454
}
@@ -1431,38 +1464,45 @@ impl fmt::Debug for DisplayUnionType<'_> {
14311464
Display::fmt(self, f)
14321465
}
14331466
}
1434-
1435-
struct DisplayUnionOmitted {
1436-
count: usize,
1437-
}
1438-
1439-
impl Display for DisplayUnionOmitted {
1440-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1441-
let plural = if self.count == 1 {
1442-
"element"
1443-
} else {
1444-
"elements"
1445-
};
1446-
write!(f, "... omitted {} union {}", self.count, plural)
1447-
}
1448-
}
1449-
14501467
struct DisplayLiteralGroup<'db> {
14511468
literals: Vec<Type<'db>>,
14521469
db: &'db dyn Db,
14531470
settings: DisplaySettings<'db>,
14541471
}
14551472

1473+
const LITERAL_POLICY: TruncationPolicy = TruncationPolicy {
1474+
max: 7,
1475+
max_when_elided: 5,
1476+
};
1477+
14561478
impl Display for DisplayLiteralGroup<'_> {
14571479
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
14581480
f.write_str("Literal[")?;
1459-
f.join(", ")
1460-
.entries(
1461-
self.literals
1462-
.iter()
1463-
.map(|ty| ty.representation(self.db, self.settings.singleline())),
1464-
)
1465-
.finish()?;
1481+
1482+
let total_entries = self.literals.len();
1483+
1484+
let display_limit =
1485+
LITERAL_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
1486+
1487+
let mut join = f.join(", ");
1488+
1489+
for lit in self.literals.iter().take(display_limit) {
1490+
let rep = lit.representation(self.db, self.settings.singleline());
1491+
join.entry(&rep);
1492+
}
1493+
1494+
if !self.settings.preserve_full_unions {
1495+
let omitted_entries = total_entries.saturating_sub(display_limit);
1496+
if omitted_entries > 0 {
1497+
join.entry(&DisplayOmitted {
1498+
count: omitted_entries,
1499+
singular: "literal",
1500+
plural: "literals",
1501+
});
1502+
}
1503+
}
1504+
1505+
join.finish()?;
14661506
f.write_str("]")
14671507
}
14681508
}

0 commit comments

Comments
 (0)