mirror of https://github.com/astral-sh/ruff
[ty] Truncate Literal type display in some situations (#20928)
This commit is contained in:
parent
baaa8dad3a
commit
fc3b341529
|
|
@ -138,3 +138,27 @@ def _(n: int):
|
||||||
# error: [unknown-argument]
|
# error: [unknown-argument]
|
||||||
y = f("foo", name="bar", unknown="quux")
|
y = f("foo", name="bar", unknown="quux")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Truncation for long unions and literals
|
||||||
|
|
||||||
|
This test demonstrates a call where the expected type is a large mixed union. The diagnostic must
|
||||||
|
therefore truncate the long expected union type to avoid overwhelming output.
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Literal, Union
|
||||||
|
|
||||||
|
class A: ...
|
||||||
|
class B: ...
|
||||||
|
class C: ...
|
||||||
|
class D: ...
|
||||||
|
class E: ...
|
||||||
|
class F: ...
|
||||||
|
|
||||||
|
def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def _(n: int):
|
||||||
|
x = n
|
||||||
|
# error: [invalid-argument-type]
|
||||||
|
f1(x)
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
---
|
||||||
|
source: crates/ty_test/src/lib.rs
|
||||||
|
assertion_line: 427
|
||||||
|
expression: snapshot
|
||||||
|
---
|
||||||
|
---
|
||||||
|
mdtest name: union_call.md - Calling a union of function types - Try to cover all possible reasons - Truncation for long unions and literals
|
||||||
|
mdtest path: crates/ty_python_semantic/resources/mdtest/diagnostics/union_call.md
|
||||||
|
---
|
||||||
|
|
||||||
|
# Python source files
|
||||||
|
|
||||||
|
## mdtest_snippet.py
|
||||||
|
|
||||||
|
```
|
||||||
|
1 | from typing import Literal, Union
|
||||||
|
2 |
|
||||||
|
3 | class A: ...
|
||||||
|
4 | class B: ...
|
||||||
|
5 | class C: ...
|
||||||
|
6 | class D: ...
|
||||||
|
7 | class E: ...
|
||||||
|
8 | class F: ...
|
||||||
|
9 |
|
||||||
|
10 | def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||||
|
11 | return 0
|
||||||
|
12 |
|
||||||
|
13 | def _(n: int):
|
||||||
|
14 | x = n
|
||||||
|
15 | # error: [invalid-argument-type]
|
||||||
|
16 | f1(x)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Diagnostics
|
||||||
|
|
||||||
|
```
|
||||||
|
error[invalid-argument-type]: Argument to function `f1` is incorrect
|
||||||
|
--> src/mdtest_snippet.py:16:8
|
||||||
|
|
|
||||||
|
14 | x = n
|
||||||
|
15 | # error: [invalid-argument-type]
|
||||||
|
16 | f1(x)
|
||||||
|
| ^ Expected `Literal[1, 2, 3, 4, 5, ... omitted 3 literals] | A | B | ... omitted 4 union elements`, found `int`
|
||||||
|
|
|
||||||
|
info: Function defined here
|
||||||
|
--> src/mdtest_snippet.py:10:5
|
||||||
|
|
|
||||||
|
8 | class F: ...
|
||||||
|
9 |
|
||||||
|
10 | def f1(x: Union[Literal[1, 2, 3, 4, 5, 6, 7, 8], A, B, C, D, E, F]) -> int:
|
||||||
|
| ^^ ----------------------------------------------------------- Parameter declared here
|
||||||
|
11 | return 0
|
||||||
|
|
|
||||||
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -38,7 +38,7 @@ 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<FxHashMap<&'db str, QualificationLevel>>,
|
pub qualified: Rc<FxHashMap<&'db str, QualificationLevel>>,
|
||||||
/// Whether long unions are displayed in full
|
/// Whether long unions and literals are displayed in full
|
||||||
pub preserve_full_unions: bool,
|
pub preserve_full_unions: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1328,6 +1328,44 @@ impl Display for DisplayParameter<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
struct TruncationPolicy {
|
||||||
|
max: usize,
|
||||||
|
max_when_elided: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TruncationPolicy {
|
||||||
|
fn display_limit(self, total: usize, preserve_full: bool) -> usize {
|
||||||
|
if preserve_full {
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
let limit = if total > self.max {
|
||||||
|
self.max_when_elided
|
||||||
|
} else {
|
||||||
|
self.max
|
||||||
|
};
|
||||||
|
limit.min(total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct DisplayOmitted {
|
||||||
|
count: usize,
|
||||||
|
singular: &'static str,
|
||||||
|
plural: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DisplayOmitted {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
let noun = if self.count == 1 {
|
||||||
|
self.singular
|
||||||
|
} else {
|
||||||
|
self.plural
|
||||||
|
};
|
||||||
|
write!(f, "... omitted {} {}", self.count, noun)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'db> UnionType<'db> {
|
impl<'db> UnionType<'db> {
|
||||||
fn display_with(
|
fn display_with(
|
||||||
&'db self,
|
&'db self,
|
||||||
|
|
@ -1348,8 +1386,10 @@ struct DisplayUnionType<'db> {
|
||||||
settings: DisplaySettings<'db>,
|
settings: DisplaySettings<'db>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_DISPLAYED_UNION_ITEMS: usize = 5;
|
const UNION_POLICY: TruncationPolicy = TruncationPolicy {
|
||||||
const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3;
|
max: 5,
|
||||||
|
max_when_elided: 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 {
|
||||||
|
|
@ -1379,16 +1419,8 @@ impl Display for DisplayUnionType<'_> {
|
||||||
|
|
||||||
let mut join = f.join(" | ");
|
let mut join = f.join(" | ");
|
||||||
|
|
||||||
let display_limit = if self.settings.preserve_full_unions {
|
let display_limit =
|
||||||
total_entries
|
UNION_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
|
||||||
} 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;
|
let mut displayed_entries = 0usize;
|
||||||
|
|
@ -1420,8 +1452,10 @@ impl Display for DisplayUnionType<'_> {
|
||||||
if !self.settings.preserve_full_unions {
|
if !self.settings.preserve_full_unions {
|
||||||
let omitted_entries = total_entries.saturating_sub(displayed_entries);
|
let omitted_entries = total_entries.saturating_sub(displayed_entries);
|
||||||
if omitted_entries > 0 {
|
if omitted_entries > 0 {
|
||||||
join.entry(&DisplayUnionOmitted {
|
join.entry(&DisplayOmitted {
|
||||||
count: omitted_entries,
|
count: omitted_entries,
|
||||||
|
singular: "union element",
|
||||||
|
plural: "union elements",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1437,38 +1471,45 @@ impl fmt::Debug for DisplayUnionType<'_> {
|
||||||
Display::fmt(self, f)
|
Display::fmt(self, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
||||||
settings: DisplaySettings<'db>,
|
settings: DisplaySettings<'db>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LITERAL_POLICY: TruncationPolicy = TruncationPolicy {
|
||||||
|
max: 7,
|
||||||
|
max_when_elided: 5,
|
||||||
|
};
|
||||||
|
|
||||||
impl Display for DisplayLiteralGroup<'_> {
|
impl Display for DisplayLiteralGroup<'_> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str("Literal[")?;
|
f.write_str("Literal[")?;
|
||||||
f.join(", ")
|
|
||||||
.entries(
|
let total_entries = self.literals.len();
|
||||||
self.literals
|
|
||||||
.iter()
|
let display_limit =
|
||||||
.map(|ty| ty.representation(self.db, self.settings.singleline())),
|
LITERAL_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
|
||||||
)
|
|
||||||
.finish()?;
|
let mut join = f.join(", ");
|
||||||
|
|
||||||
|
for lit in self.literals.iter().take(display_limit) {
|
||||||
|
let rep = lit.representation(self.db, self.settings.singleline());
|
||||||
|
join.entry(&rep);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.settings.preserve_full_unions {
|
||||||
|
let omitted_entries = total_entries.saturating_sub(display_limit);
|
||||||
|
if omitted_entries > 0 {
|
||||||
|
join.entry(&DisplayOmitted {
|
||||||
|
count: omitted_entries,
|
||||||
|
singular: "literal",
|
||||||
|
plural: "literals",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
join.finish()?;
|
||||||
f.write_str("]")
|
f.write_str("]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue