mirror of
https://github.com/astral-sh/ruff
synced 2026-01-21 13:30:49 -05:00
[ty]: consolidate type[] types in a union when displaying them in diagnostics (#22592)
This commit is contained in:
@@ -235,8 +235,8 @@ Foo(3.14)
|
||||
Foo(42)
|
||||
Foo("hello") # error: [invalid-argument-type] "Argument is incorrect: Expected `int | float`, found `Literal["hello"]`"
|
||||
|
||||
reveal_type(Foo(3.14).__class__) # revealed: type[int] | type[float]
|
||||
reveal_type(Foo(42).__class__) # revealed: type[int] | type[float]
|
||||
reveal_type(Foo(3.14).__class__) # revealed: type[int | float]
|
||||
reveal_type(Foo(42).__class__) # revealed: type[int | float]
|
||||
static_assert(is_assignable_to(Foo, float))
|
||||
static_assert(is_assignable_to(Foo, int | float))
|
||||
static_assert(is_assignable_to(Foo, int | float | None))
|
||||
@@ -253,9 +253,9 @@ Bar(3.14)
|
||||
Bar(42)
|
||||
Bar("goodbye") # error: [invalid-argument-type]
|
||||
|
||||
reveal_type(Bar(1 + 2j).__class__) # revealed: type[int] | type[float] | type[complex]
|
||||
reveal_type(Bar(3.14).__class__) # revealed: type[int] | type[float] | type[complex]
|
||||
reveal_type(Bar(42).__class__) # revealed: type[int] | type[float] | type[complex]
|
||||
reveal_type(Bar(1 + 2j).__class__) # revealed: type[int | float | complex]
|
||||
reveal_type(Bar(3.14).__class__) # revealed: type[int | float | complex]
|
||||
reveal_type(Bar(42).__class__) # revealed: type[int | float | complex]
|
||||
static_assert(is_assignable_to(Bar, complex))
|
||||
static_assert(is_assignable_to(Bar, int | float | complex))
|
||||
static_assert(is_assignable_to(Bar, int | float | complex | None))
|
||||
|
||||
@@ -2094,8 +2094,8 @@ def f(a: int, b: typing_extensions.LiteralString, c: int | str, d: type[str]):
|
||||
reveal_type(b.__class__) # revealed: <class 'str'>
|
||||
reveal_type(type(b)) # revealed: <class 'str'>
|
||||
|
||||
reveal_type(c.__class__) # revealed: type[int] | type[str]
|
||||
reveal_type(type(c)) # revealed: type[int] | type[str]
|
||||
reveal_type(c.__class__) # revealed: type[int | str]
|
||||
reveal_type(type(c)) # revealed: type[int | str]
|
||||
|
||||
# `type[type]`, a.k.a., either the class `type` or some subclass of `type`.
|
||||
# It would be incorrect to infer `Literal[type]` here,
|
||||
|
||||
@@ -408,7 +408,7 @@ def f(x: type[B]) -> B: ...
|
||||
from overloaded import A, B, f
|
||||
|
||||
def _(x: type[A | B]):
|
||||
reveal_type(x) # revealed: type[A] | type[B]
|
||||
reveal_type(x) # revealed: type[A | B]
|
||||
reveal_type(f(x)) # revealed: A | B
|
||||
reveal_type(f(*(x,))) # revealed: A | B
|
||||
```
|
||||
|
||||
@@ -1139,8 +1139,8 @@ SubclassOfP = type[P]
|
||||
reveal_type(SubclassOfA) # revealed: <special-form 'type[A]'>
|
||||
reveal_type(SubclassOfAny) # revealed: <special-form 'type[Any]'>
|
||||
reveal_type(SubclassOfAOrB1) # revealed: <special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfG) # revealed: <special-form 'type[G[Unknown]]'>
|
||||
reveal_type(SubclassOfGInt) # revealed: <special-form 'type[G[int]]'>
|
||||
reveal_type(SubclassOfP) # revealed: <special-form 'type[P]'>
|
||||
@@ -1161,13 +1161,13 @@ def _(
|
||||
reveal_type(subclass_of_any) # revealed: type[Any]
|
||||
reveal_type(subclass_of_any()) # revealed: Any
|
||||
|
||||
reveal_type(subclass_of_a_or_b1) # revealed: type[A] | type[B]
|
||||
reveal_type(subclass_of_a_or_b1) # revealed: type[A | B]
|
||||
reveal_type(subclass_of_a_or_b1()) # revealed: A | B
|
||||
|
||||
reveal_type(subclass_of_a_or_b2) # revealed: type[A] | type[B]
|
||||
reveal_type(subclass_of_a_or_b2) # revealed: type[A | B]
|
||||
reveal_type(subclass_of_a_or_b2()) # revealed: A | B
|
||||
|
||||
reveal_type(subclass_of_a_or_b3) # revealed: type[A] | type[B]
|
||||
reveal_type(subclass_of_a_or_b3) # revealed: type[A | B]
|
||||
reveal_type(subclass_of_a_or_b3()) # revealed: A | B
|
||||
|
||||
reveal_type(subclass_of_g) # revealed: type[G[Unknown]]
|
||||
@@ -1200,10 +1200,10 @@ def _(
|
||||
subclass_of_union_alias1: SubclassOfUnionAlias1,
|
||||
subclass_of_union_alias2: SubclassOfUnionAlias2,
|
||||
):
|
||||
reveal_type(subclass_of_union_alias1) # revealed: type[C] | type[D]
|
||||
reveal_type(subclass_of_union_alias1) # revealed: type[C | D]
|
||||
reveal_type(subclass_of_union_alias1()) # revealed: C | D
|
||||
|
||||
reveal_type(subclass_of_union_alias2) # revealed: type[C] | type[D]
|
||||
reveal_type(subclass_of_union_alias2) # revealed: type[C | D]
|
||||
reveal_type(subclass_of_union_alias2()) # revealed: C | D
|
||||
```
|
||||
|
||||
@@ -1255,8 +1255,8 @@ SubclassOfP = Type[P]
|
||||
reveal_type(SubclassOfA) # revealed: <special-form 'type[A]'>
|
||||
reveal_type(SubclassOfAny) # revealed: <special-form 'type[Any]'>
|
||||
reveal_type(SubclassOfAOrB1) # revealed: <special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special-form 'type[A] | type[B]'>
|
||||
reveal_type(SubclassOfAOrB2) # revealed: <types.UnionType special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfAOrB3) # revealed: <types.UnionType special-form 'type[A | B]'>
|
||||
reveal_type(SubclassOfG) # revealed: <special-form 'type[G[Unknown]]'>
|
||||
reveal_type(SubclassOfGInt) # revealed: <special-form 'type[G[int]]'>
|
||||
reveal_type(SubclassOfP) # revealed: <special-form 'type[P]'>
|
||||
@@ -1277,13 +1277,13 @@ def _(
|
||||
reveal_type(subclass_of_any) # revealed: type[Any]
|
||||
reveal_type(subclass_of_any()) # revealed: Any
|
||||
|
||||
reveal_type(subclass_of_a_or_b1) # revealed: type[A] | type[B]
|
||||
reveal_type(subclass_of_a_or_b1) # revealed: type[A | B]
|
||||
reveal_type(subclass_of_a_or_b1()) # revealed: A | B
|
||||
|
||||
reveal_type(subclass_of_a_or_b2) # revealed: type[A] | type[B]
|
||||
reveal_type(subclass_of_a_or_b2) # revealed: type[A | B]
|
||||
reveal_type(subclass_of_a_or_b2()) # revealed: A | B
|
||||
|
||||
reveal_type(subclass_of_a_or_b3) # revealed: type[A] | type[B]
|
||||
reveal_type(subclass_of_a_or_b3) # revealed: type[A | B]
|
||||
reveal_type(subclass_of_a_or_b3()) # revealed: A | B
|
||||
|
||||
reveal_type(subclass_of_g) # revealed: type[G[Unknown]]
|
||||
|
||||
@@ -141,7 +141,7 @@ python-version = "3.10"
|
||||
```py
|
||||
def f(x: type[int | str | bytes | range]):
|
||||
if issubclass(x, int | str):
|
||||
reveal_type(x) # revealed: type[int] | type[str]
|
||||
reveal_type(x) # revealed: type[int | str]
|
||||
elif issubclass(x, bytes | memoryview):
|
||||
reveal_type(x) # revealed: type[bytes]
|
||||
else:
|
||||
@@ -154,7 +154,7 @@ runtime a special exception is made for `None` so that `issubclass(x, int | None
|
||||
```py
|
||||
def _(x: type):
|
||||
if issubclass(x, int | str | None):
|
||||
reveal_type(x) # revealed: type[int] | type[str] | <class 'NoneType'>
|
||||
reveal_type(x) # revealed: type[int | str] | <class 'NoneType'>
|
||||
else:
|
||||
reveal_type(x) # revealed: type & ~type[int] & ~type[str] & ~<class 'NoneType'>
|
||||
```
|
||||
@@ -176,9 +176,9 @@ python-version = "3.10"
|
||||
def _(x: type[int | list | bytes]):
|
||||
# error: [invalid-argument-type]
|
||||
if issubclass(x, int | list[int]):
|
||||
reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
reveal_type(x) # revealed: type[int | list[Unknown] | bytes]
|
||||
else:
|
||||
reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
reveal_type(x) # revealed: type[int | list[Unknown] | bytes]
|
||||
```
|
||||
|
||||
## PEP-604 unions on Python \<3.10
|
||||
@@ -213,7 +213,7 @@ reveal_type(IntOrStr) # revealed: <types.UnionType special-form 'int | str'>
|
||||
|
||||
def f(x: type[int | str | bytes | range]):
|
||||
if issubclass(x, IntOrStr):
|
||||
reveal_type(x) # revealed: type[int] | type[str]
|
||||
reveal_type(x) # revealed: type[int | str]
|
||||
elif issubclass(x, Union[bytes, memoryview]):
|
||||
reveal_type(x) # revealed: type[bytes]
|
||||
else:
|
||||
|
||||
@@ -262,11 +262,11 @@ def _(
|
||||
af: type[AmbiguousClass] | type[FalsyClass],
|
||||
flag: bool,
|
||||
):
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | type[AmbiguousClass]
|
||||
reveal_type(ta) # revealed: type[TruthyClass | AmbiguousClass]
|
||||
if ta:
|
||||
reveal_type(ta) # revealed: type[TruthyClass] | (type[AmbiguousClass] & ~AlwaysFalsy)
|
||||
|
||||
reveal_type(af) # revealed: type[AmbiguousClass] | type[FalsyClass]
|
||||
reveal_type(af) # revealed: type[AmbiguousClass | FalsyClass]
|
||||
if af:
|
||||
reveal_type(af) # revealed: type[AmbiguousClass] & ~AlwaysFalsy
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/narrow/issubclass.md
|
||||
1 | def _(x: type[int | list | bytes]):
|
||||
2 | # error: [invalid-argument-type]
|
||||
3 | if issubclass(x, int | list[int]):
|
||||
4 | reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
4 | reveal_type(x) # revealed: type[int | list[Unknown] | bytes]
|
||||
5 | else:
|
||||
6 | reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
6 | reveal_type(x) # revealed: type[int | list[Unknown] | bytes]
|
||||
```
|
||||
|
||||
# Diagnostics
|
||||
@@ -33,7 +33,7 @@ error[invalid-argument-type]: Invalid second argument to `issubclass`
|
||||
| ^^^^^^^^^^^^^^---------------^
|
||||
| |
|
||||
| This `UnionType` instance contains non-class elements
|
||||
4 | reveal_type(x) # revealed: type[int] | type[list[Unknown]] | type[bytes]
|
||||
4 | reveal_type(x) # revealed: type[int | list[Unknown] | bytes]
|
||||
5 | else:
|
||||
|
|
||||
info: A `UnionType` instance can only be used as the second argument to `issubclass` if all elements are class objects
|
||||
|
||||
@@ -93,7 +93,7 @@ class A:
|
||||
class C: ...
|
||||
|
||||
def _(u: type[BasicUser | ProUser | A.B.C]):
|
||||
# revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
# revealed: type[BasicUser | ProUser | C]
|
||||
reveal_type(u)
|
||||
```
|
||||
|
||||
@@ -110,9 +110,9 @@ class A:
|
||||
class C: ...
|
||||
|
||||
def f(a: type[Union[BasicUser, ProUser, A.B.C]], b: type[Union[str]], c: type[Union[BasicUser, Union[ProUser, A.B.C]]]):
|
||||
reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(a) # revealed: type[BasicUser | ProUser | C]
|
||||
reveal_type(b) # revealed: type[str]
|
||||
reveal_type(c) # revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(c) # revealed: type[BasicUser | ProUser | C]
|
||||
```
|
||||
|
||||
## New-style and old-style unions in combination
|
||||
@@ -128,8 +128,8 @@ class A:
|
||||
class C: ...
|
||||
|
||||
def f(a: type[BasicUser | Union[ProUser, A.B.C]], b: type[Union[BasicUser | Union[ProUser, A.B.C | str]]]):
|
||||
reveal_type(a) # revealed: type[BasicUser] | type[ProUser] | type[C]
|
||||
reveal_type(b) # revealed: type[BasicUser] | type[ProUser] | type[C] | type[str]
|
||||
reveal_type(a) # revealed: type[BasicUser | ProUser | C]
|
||||
reveal_type(b) # revealed: type[BasicUser | ProUser | C | str]
|
||||
```
|
||||
|
||||
## Illegal parameters
|
||||
|
||||
@@ -7,9 +7,10 @@ This test suite covers certain basic properties and simplification strategies fo
|
||||
```py
|
||||
from typing import Literal
|
||||
|
||||
def _(u1: int | str, u2: Literal[0] | Literal[1]) -> None:
|
||||
def _(u1: int | str, u2: Literal[0] | Literal[1], u3: type[int] | type[str]) -> None:
|
||||
reveal_type(u1) # revealed: int | str
|
||||
reveal_type(u2) # revealed: Literal[0, 1]
|
||||
reveal_type(u3) # revealed: type[int | str]
|
||||
```
|
||||
|
||||
## Duplicate elements are collapsed
|
||||
|
||||
@@ -28,8 +28,8 @@ use crate::types::visitor::TypeVisitor;
|
||||
use crate::types::{
|
||||
BindingContext, BoundTypeVarIdentity, CallableType, CallableTypeKind, IntersectionType,
|
||||
KnownBoundMethodType, KnownClass, KnownInstanceType, MaterializationKind, Protocol,
|
||||
ProtocolInstanceType, SpecialFormType, StringLiteralType, SubclassOfInner, Type, TypeGuardLike,
|
||||
TypedDictType, UnionType, WrapperDescriptorKind, visitor,
|
||||
ProtocolInstanceType, SpecialFormType, StringLiteralType, SubclassOfInner, SubclassOfType,
|
||||
Type, TypeGuardLike, TypedDictType, UnionType, WrapperDescriptorKind, visitor,
|
||||
};
|
||||
|
||||
/// Settings for displaying types and signatures
|
||||
@@ -2151,15 +2151,20 @@ impl<'db> FmtDetailed<'db> for DisplayUnionType<'_, 'db> {
|
||||
}
|
||||
|
||||
let elements = self.ty.elements(self.db);
|
||||
let mut condensed_types = vec![];
|
||||
let mut subclass_of_types = vec![];
|
||||
|
||||
let condensed_types = elements
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|element| is_condensable(*element))
|
||||
.collect::<Vec<_>>();
|
||||
for element in elements.iter().copied() {
|
||||
if is_condensable(element) {
|
||||
condensed_types.push(element);
|
||||
} else if let Type::SubclassOf(subclass_of) = element {
|
||||
subclass_of_types.push(subclass_of);
|
||||
}
|
||||
}
|
||||
|
||||
let total_entries =
|
||||
usize::from(!condensed_types.is_empty()) + elements.len() - condensed_types.len();
|
||||
let total_entries = elements.len() - condensed_types.len() - subclass_of_types.len()
|
||||
+ usize::from(!condensed_types.is_empty())
|
||||
+ usize::from(!subclass_of_types.is_empty());
|
||||
|
||||
assert_ne!(total_entries, 0);
|
||||
|
||||
@@ -2170,6 +2175,7 @@ impl<'db> FmtDetailed<'db> for DisplayUnionType<'_, 'db> {
|
||||
UNION_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
|
||||
|
||||
let mut condensed_types = Some(condensed_types);
|
||||
let mut subclass_of_types = Some(subclass_of_types);
|
||||
let mut displayed_entries = 0usize;
|
||||
|
||||
for element in elements {
|
||||
@@ -2186,6 +2192,15 @@ impl<'db> FmtDetailed<'db> for DisplayUnionType<'_, 'db> {
|
||||
settings: self.settings.singleline(),
|
||||
});
|
||||
}
|
||||
} else if element.is_subclass_of() {
|
||||
if let Some(subclass_of_types) = subclass_of_types.take() {
|
||||
displayed_entries += 1;
|
||||
join.entry(&DisplaySubclassOfGroup {
|
||||
types: subclass_of_types,
|
||||
db: self.db,
|
||||
settings: self.settings.singleline(),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
displayed_entries += 1;
|
||||
join.entry(&DisplayMaybeParenthesizedType {
|
||||
@@ -2221,6 +2236,61 @@ impl fmt::Debug for DisplayUnionType<'_, '_> {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplaySubclassOfGroup<'db> {
|
||||
types: Vec<SubclassOfType<'db>>,
|
||||
db: &'db dyn Db,
|
||||
settings: DisplaySettings<'db>,
|
||||
}
|
||||
|
||||
impl<'db> FmtDetailed<'db> for DisplaySubclassOfGroup<'db> {
|
||||
fn fmt_detailed(&self, f: &mut TypeWriter<'_, '_, 'db>) -> fmt::Result {
|
||||
f.write_str("type[")?;
|
||||
let total_entries = self.types.len();
|
||||
let display_limit =
|
||||
UNION_POLICY.display_limit(total_entries, self.settings.preserve_full_unions);
|
||||
let mut join = f.join(" | ");
|
||||
for subclass_of in self.types.iter().take(display_limit) {
|
||||
match subclass_of.subclass_of() {
|
||||
SubclassOfInner::Class(ClassType::NonGeneric(class)) => {
|
||||
join.entry(&class.display_with(self.db, self.settings.singleline()));
|
||||
}
|
||||
SubclassOfInner::Class(ClassType::Generic(alias)) => {
|
||||
join.entry(&alias.display_with(self.db, self.settings.singleline()));
|
||||
}
|
||||
SubclassOfInner::Dynamic(dynamic) => {
|
||||
let rep =
|
||||
Type::Dynamic(dynamic).representation(self.db, self.settings.singleline());
|
||||
join.entry(&rep);
|
||||
}
|
||||
SubclassOfInner::TypeVar(bound_typevar) => {
|
||||
let rep = Type::TypeVar(bound_typevar)
|
||||
.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: "type",
|
||||
plural: "types",
|
||||
});
|
||||
}
|
||||
}
|
||||
join.finish()?;
|
||||
f.write_str("]")
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for DisplaySubclassOfGroup<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
self.fmt_detailed(&mut TypeWriter::Formatter(f))
|
||||
}
|
||||
}
|
||||
|
||||
struct DisplayLiteralGroup<'db> {
|
||||
literals: Vec<Type<'db>>,
|
||||
db: &'db dyn Db,
|
||||
|
||||
Reference in New Issue
Block a user