diff --git a/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 b/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 index 929ad29c37..41b82b238e 100644 --- a/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 +++ b/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 @@ -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: 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 ``` @@ -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: 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 ``` @@ -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: 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 ``` @@ -152,7 +152,7 @@ info: Overload implementation defined here 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: 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 ``` @@ -176,7 +176,7 @@ info: Function defined here 8 | return 0 | 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 ``` @@ -200,7 +200,7 @@ info: Type variable defined here 14 | return 0 | 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 ``` @@ -227,7 +227,7 @@ info: Matching overload defined here info: Non-matching overloads for function `f5`: info: () -> None 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 ``` @@ -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: 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 ``` diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index f20ebefd21..2c52f40198 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -35,6 +35,8 @@ pub struct DisplaySettings<'db> { /// Class names that should be displayed fully qualified /// (e.g., `module.ClassName` instead of just `ClassName`) pub qualified: Rc>, + /// Whether long unions are displayed in full + pub preserve_full_unions: bool, } 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] pub fn from_possibly_ambiguous_type_pair( db: &'db dyn Db, @@ -1265,6 +1283,9 @@ struct DisplayUnionType<'db> { settings: DisplaySettings<'db>, } +const MAX_DISPLAYED_UNION_ITEMS: usize = 5; +const MAX_DISPLAYED_UNION_ITEMS_WHEN_ELIDED: usize = 3; + impl Display for DisplayUnionType<'_> { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn is_condensable(ty: Type<'_>) -> bool { @@ -1286,12 +1307,35 @@ impl Display for DisplayUnionType<'_> { .filter(|element| is_condensable(*element)) .collect::>(); + 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 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 displayed_entries = 0usize; + for element in elements { + if displayed_entries >= display_limit { + break; + } + if is_condensable(*element) { if let Some(condensed_types) = condensed_types.take() { + displayed_entries += 1; join.entry(&DisplayLiteralGroup { literals: condensed_types, db: self.db, @@ -1299,6 +1343,7 @@ impl Display for DisplayUnionType<'_> { }); } } else { + displayed_entries += 1; join.entry(&DisplayMaybeParenthesizedType { ty: *element, 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()?; 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> { literals: Vec>, db: &'db dyn Db, diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index 2b06665452..4488a77d25 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -72,6 +72,7 @@ use crate::types::diagnostic::{ report_bad_argument_to_get_protocol_members, report_bad_argument_to_protocol_interface, report_runtime_check_against_non_runtime_checkable_protocol, }; +use crate::types::display::DisplaySettings; use crate::types::generics::GenericContext; use crate::types::narrow::ClassInfoConstraintFunction; use crate::types::signatures::{CallableSignature, Signature}; @@ -1386,10 +1387,11 @@ impl KnownFunction { { let mut diag = builder.into_diagnostic("Revealed type"); let span = context.span(&call_expression.arguments.args[0]); - diag.annotate( - Annotation::primary(span) - .message(format_args!("`{}`", revealed_type.display(db))), - ); + diag.annotate(Annotation::primary(span).message(format_args!( + "`{}`", + revealed_type + .display_with(db, DisplaySettings::default().preserve_long_unions()) + ))); } }