take 7?
This commit is contained in:
@@ -58,22 +58,7 @@ reveal_type(c >= d) # revealed: Literal[True]
|
||||
#### Results with Ambiguity
|
||||
|
||||
```py
|
||||
class P:
|
||||
def __lt__(self, other: "P") -> bool:
|
||||
return True
|
||||
|
||||
def __le__(self, other: "P") -> bool:
|
||||
return True
|
||||
|
||||
def __gt__(self, other: "P") -> bool:
|
||||
return True
|
||||
|
||||
def __ge__(self, other: "P") -> bool:
|
||||
return True
|
||||
|
||||
class Q(P): ...
|
||||
|
||||
def _(x: P, y: Q):
|
||||
def _(x: bool, y: int):
|
||||
a = (x,)
|
||||
b = (y,)
|
||||
|
||||
|
||||
@@ -455,9 +455,9 @@ else:
|
||||
reveal_type(x) # revealed: slice
|
||||
finally:
|
||||
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float | range | slice`
|
||||
reveal_type(x) # revealed: bool | slice | float
|
||||
reveal_type(x) # revealed: bool | float | slice
|
||||
|
||||
reveal_type(x) # revealed: bool | slice | float
|
||||
reveal_type(x) # revealed: bool | float | slice
|
||||
```
|
||||
|
||||
## Nested `try`/`except` blocks
|
||||
@@ -534,7 +534,7 @@ try:
|
||||
reveal_type(x) # revealed: slice
|
||||
finally:
|
||||
# TODO: should be `Literal[1] | str | bytes | bool | memoryview | float | range | slice`
|
||||
reveal_type(x) # revealed: bool | slice | float
|
||||
reveal_type(x) # revealed: bool | float | slice
|
||||
x = 2
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
reveal_type(x) # revealed: Literal[2]
|
||||
|
||||
@@ -21,22 +21,22 @@ else:
|
||||
if x and not x:
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
|
||||
|
||||
if not (x and not x):
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if x or not x:
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
|
||||
else:
|
||||
reveal_type(x) # revealed: Never
|
||||
|
||||
if not (x or not x):
|
||||
reveal_type(x) # revealed: Never
|
||||
else:
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | tuple[()] | None
|
||||
reveal_type(x) # revealed: Literal[0, -1, "", "foo", b"", b"bar"] | bool | None | tuple[()]
|
||||
|
||||
if (isinstance(x, int) or isinstance(x, str)) and x:
|
||||
reveal_type(x) # revealed: Literal[-1, True, "foo"]
|
||||
|
||||
@@ -358,4 +358,12 @@ from knot_extensions import is_assignable_to, static_assert
|
||||
static_assert(is_assignable_to(bool, str | bool))
|
||||
```
|
||||
|
||||
### `bool` is assignable to `AlwaysTruthy | AlwaysFalsy`
|
||||
|
||||
```py
|
||||
from knot_extensions import static_assert, is_assignable_to, AlwaysTruthy, AlwaysFalsy
|
||||
|
||||
static_assert(is_assignable_to(bool, AlwaysTruthy | AlwaysFalsy))
|
||||
```
|
||||
|
||||
[typing documentation]: https://typing.readthedocs.io/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
|
||||
|
||||
@@ -824,11 +824,9 @@ impl<'db> Type<'db> {
|
||||
/// - Literal[True, False] | T <: bool | T
|
||||
#[must_use]
|
||||
pub fn with_normalized_bools(self, db: &'db dyn Db) -> Self {
|
||||
const LITERAL_BOOLS: [Type; 2] = [Type::BooleanLiteral(false), Type::BooleanLiteral(true)];
|
||||
|
||||
match self {
|
||||
Type::Instance(InstanceType { class }) if class.is_known(db, KnownClass::Bool) => {
|
||||
Type::Union(UnionType::new(db, Box::from(LITERAL_BOOLS)))
|
||||
Type::normalized_bool(db)
|
||||
}
|
||||
// TODO: decompose `LiteralString` into `Literal[""] | TruthyLiteralString`?
|
||||
// We'd need to rename this method... --Alex
|
||||
@@ -884,12 +882,6 @@ impl<'db> Type<'db> {
|
||||
return true;
|
||||
}
|
||||
|
||||
let normalized_self = self.with_normalized_bools(db);
|
||||
let normalized_target = target.with_normalized_bools(db);
|
||||
if normalized_self != self || normalized_target != target {
|
||||
return normalized_self.is_subtype_of(db, normalized_target);
|
||||
}
|
||||
|
||||
// Non-fully-static types do not participate in subtyping.
|
||||
//
|
||||
// Type `A` can only be a subtype of type `B` if the set of possible runtime objects
|
||||
@@ -912,6 +904,13 @@ impl<'db> Type<'db> {
|
||||
(Type::Never, _) => true,
|
||||
(_, Type::Never) => false,
|
||||
|
||||
(Type::Instance(InstanceType { class }), _) if class.is_known(db, KnownClass::Bool) => {
|
||||
Type::normalized_bool(db).is_subtype_of(db, target)
|
||||
}
|
||||
(_, Type::Instance(InstanceType { class })) if class.is_known(db, KnownClass::Bool) => {
|
||||
self.is_boolean_literal()
|
||||
}
|
||||
|
||||
(Type::Union(union), _) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
@@ -1108,11 +1107,7 @@ impl<'db> Type<'db> {
|
||||
if self.is_gradual_equivalent_to(db, target) {
|
||||
return true;
|
||||
}
|
||||
let normalized_self = self.with_normalized_bools(db);
|
||||
let normalized_target = target.with_normalized_bools(db);
|
||||
if normalized_self != self || normalized_target != target {
|
||||
return normalized_self.is_assignable_to(db, normalized_target);
|
||||
}
|
||||
|
||||
match (self, target) {
|
||||
// Never can be assigned to any type.
|
||||
(Type::Never, _) => true,
|
||||
@@ -1129,6 +1124,13 @@ impl<'db> Type<'db> {
|
||||
true
|
||||
}
|
||||
|
||||
(Type::Instance(InstanceType { class }), _) if class.is_known(db, KnownClass::Bool) => {
|
||||
Type::normalized_bool(db).is_assignable_to(db, target)
|
||||
}
|
||||
(_, Type::Instance(InstanceType { class })) if class.is_known(db, KnownClass::Bool) => {
|
||||
self.is_assignable_to(db, Type::normalized_bool(db))
|
||||
}
|
||||
|
||||
// A union is assignable to a type T iff every element of the union is assignable to T.
|
||||
(Type::Union(union), ty) => union
|
||||
.elements(db)
|
||||
@@ -1213,13 +1215,6 @@ impl<'db> Type<'db> {
|
||||
pub(crate) fn is_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||
// TODO equivalent but not identical types: TypedDicts, Protocols, type aliases, etc.
|
||||
|
||||
let normalized_self = self.with_normalized_bools(db);
|
||||
let normalized_other = other.with_normalized_bools(db);
|
||||
|
||||
if normalized_self != self || normalized_other != other {
|
||||
return normalized_self.is_equivalent_to(db, normalized_other);
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Type::Union(left), Type::Union(right)) => left.is_equivalent_to(db, right),
|
||||
(Type::Intersection(left), Type::Intersection(right)) => {
|
||||
@@ -1261,13 +1256,6 @@ impl<'db> Type<'db> {
|
||||
///
|
||||
/// [Summary of type relations]: https://typing.readthedocs.io/en/latest/spec/concepts.html#summary-of-type-relations
|
||||
pub(crate) fn is_gradual_equivalent_to(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||
let normalized_self = self.with_normalized_bools(db);
|
||||
let normalized_other = other.with_normalized_bools(db);
|
||||
|
||||
if normalized_self != self || normalized_other != other {
|
||||
return normalized_self.is_gradual_equivalent_to(db, normalized_other);
|
||||
}
|
||||
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
@@ -1300,12 +1288,6 @@ impl<'db> Type<'db> {
|
||||
/// Note: This function aims to have no false positives, but might return
|
||||
/// wrong `false` answers in some cases.
|
||||
pub(crate) fn is_disjoint_from(self, db: &'db dyn Db, other: Type<'db>) -> bool {
|
||||
let normalized_self = self.with_normalized_bools(db);
|
||||
let normalized_other = other.with_normalized_bools(db);
|
||||
if normalized_self != self || normalized_other != other {
|
||||
return normalized_self.is_disjoint_from(db, normalized_other);
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(Type::Never, _) | (_, Type::Never) => true,
|
||||
|
||||
@@ -2427,6 +2409,13 @@ impl<'db> Type<'db> {
|
||||
KnownClass::NoneType.to_instance(db)
|
||||
}
|
||||
|
||||
/// The type `Literal[True, False]`, which is exactly equivalent to `bool`
|
||||
/// (and which `bool` is eagerly normalized to in several situations)
|
||||
pub fn normalized_bool(db: &'db dyn Db) -> Type<'db> {
|
||||
const LITERAL_BOOLS: [Type; 2] = [Type::BooleanLiteral(false), Type::BooleanLiteral(true)];
|
||||
Type::Union(UnionType::new(db, Box::from(LITERAL_BOOLS)))
|
||||
}
|
||||
|
||||
/// Return the type of `tuple(sys.version_info)`.
|
||||
///
|
||||
/// This is not exactly the type that `sys.version_info` has at runtime,
|
||||
@@ -4698,19 +4687,18 @@ pub struct TupleType<'db> {
|
||||
}
|
||||
|
||||
impl<'db> TupleType<'db> {
|
||||
pub fn from_elements<I, T>(db: &'db dyn Db, types: I) -> Type<'db>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
T: Into<Type<'db>>,
|
||||
{
|
||||
pub fn from_elements<T: Into<Type<'db>>>(
|
||||
db: &'db dyn Db,
|
||||
types: impl IntoIterator<Item = T>,
|
||||
) -> Type<'db> {
|
||||
let mut elements = vec![];
|
||||
|
||||
for ty in types {
|
||||
let ty: Type<'db> = ty.into();
|
||||
let ty = ty.into();
|
||||
if ty.is_never() {
|
||||
return Type::Never;
|
||||
}
|
||||
elements.push(ty.with_normalized_bools(db));
|
||||
elements.push(ty);
|
||||
}
|
||||
|
||||
Type::Tuple(Self::new(db, elements.into_boxed_slice()))
|
||||
|
||||
@@ -103,10 +103,35 @@ impl<'db> UnionBuilder<'db> {
|
||||
}
|
||||
|
||||
pub(crate) fn build(self) -> Type<'db> {
|
||||
match self.elements.len() {
|
||||
let UnionBuilder { elements, db } = self;
|
||||
|
||||
match elements.len() {
|
||||
0 => Type::Never,
|
||||
1 => self.elements[0],
|
||||
_ => Type::Union(UnionType::new(self.db, self.elements.into_boxed_slice())),
|
||||
1 => elements[0],
|
||||
_ => {
|
||||
let mut normalized_elements = Vec::with_capacity(elements.len());
|
||||
let mut first_bool_literal_pos = None;
|
||||
let mut seen_two_bool_literals = false;
|
||||
for (i, element) in elements.into_iter().enumerate() {
|
||||
if element.is_boolean_literal() {
|
||||
if first_bool_literal_pos.is_none() {
|
||||
first_bool_literal_pos = Some(i);
|
||||
} else {
|
||||
seen_two_bool_literals = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
normalized_elements.push(element);
|
||||
}
|
||||
if let (Some(pos), true) = (first_bool_literal_pos, seen_two_bool_literals) {
|
||||
// If we have two boolean literals, we can merge them to `bool`.
|
||||
if normalized_elements.len() == 1 {
|
||||
return KnownClass::Bool.to_instance(db);
|
||||
}
|
||||
normalized_elements[pos] = KnownClass::Bool.to_instance(db);
|
||||
}
|
||||
Type::Union(UnionType::new(db, normalized_elements.into_boxed_slice()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! Display implementations for types.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display, Formatter, Write};
|
||||
|
||||
use ruff_db::display::FormatterJoinExtension;
|
||||
@@ -152,31 +151,12 @@ struct DisplayUnionType<'db> {
|
||||
|
||||
impl Display for DisplayUnionType<'_> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
let mut elements = Cow::Borrowed(self.ty.elements(self.db));
|
||||
|
||||
if let Some(literal_false_pos) = elements
|
||||
.iter()
|
||||
.position(|ty| matches!(ty, Type::BooleanLiteral(false)))
|
||||
{
|
||||
if let Some(literal_true_pos) = elements
|
||||
.iter()
|
||||
.position(|ty| matches!(ty, Type::BooleanLiteral(true)))
|
||||
{
|
||||
let (min, max) = if literal_false_pos < literal_true_pos {
|
||||
(literal_false_pos, literal_true_pos)
|
||||
} else {
|
||||
(literal_true_pos, literal_false_pos)
|
||||
};
|
||||
let mutable_elements = elements.to_mut();
|
||||
mutable_elements.swap_remove(max);
|
||||
mutable_elements[min] = KnownClass::Bool.to_instance(self.db);
|
||||
}
|
||||
}
|
||||
let elements = self.ty.elements(self.db);
|
||||
|
||||
// Group condensed-display types by kind.
|
||||
let mut grouped_condensed_kinds = FxHashMap::default();
|
||||
|
||||
for element in &*elements {
|
||||
for element in elements {
|
||||
if let Ok(kind) = CondensedDisplayTypeKind::try_from(*element) {
|
||||
grouped_condensed_kinds
|
||||
.entry(kind)
|
||||
@@ -187,7 +167,7 @@ impl Display for DisplayUnionType<'_> {
|
||||
|
||||
let mut join = f.join(" | ");
|
||||
|
||||
for element in &*elements {
|
||||
for element in elements {
|
||||
if let Ok(kind) = CondensedDisplayTypeKind::try_from(*element) {
|
||||
let Some(condensed_kind) = grouped_condensed_kinds.remove(&kind) else {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user