mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 05:14:52 +00:00
[red-knot] T | object == object
(#16088)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary - Simplify unions with `object` to `object`. - Add a new `Type::object(db)` constructor to abbreviate `KnownClass::Object.to_instance(db)` in some places. - Add a `Type::is_object` and `Class::is_object` function to make some tests for a bit easier to read. closes #16084 ## Test Plan New Markdown tests.
This commit is contained in:
parent
f30fac6326
commit
0019d39f6e
8 changed files with 75 additions and 29 deletions
|
@ -37,6 +37,31 @@ def noreturn(u1: int | NoReturn, u2: int | NoReturn | str) -> None:
|
||||||
reveal_type(u2) # revealed: int | str
|
reveal_type(u2) # revealed: int | str
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `object` subsumes everything
|
||||||
|
|
||||||
|
Unions with `object` can be simplified to `object`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing_extensions import Never, Any
|
||||||
|
|
||||||
|
def _(
|
||||||
|
u1: int | object,
|
||||||
|
u2: object | int,
|
||||||
|
u3: Any | object,
|
||||||
|
u4: object | Any,
|
||||||
|
u5: object | Never,
|
||||||
|
u6: Never | object,
|
||||||
|
u7: int | str | object | bytes | Any,
|
||||||
|
) -> None:
|
||||||
|
reveal_type(u1) # revealed: object
|
||||||
|
reveal_type(u2) # revealed: object
|
||||||
|
reveal_type(u3) # revealed: object
|
||||||
|
reveal_type(u4) # revealed: object
|
||||||
|
reveal_type(u5) # revealed: object
|
||||||
|
reveal_type(u6) # revealed: object
|
||||||
|
reveal_type(u7) # revealed: object
|
||||||
|
```
|
||||||
|
|
||||||
## Flattening of nested unions
|
## Flattening of nested unions
|
||||||
|
|
||||||
```py
|
```py
|
||||||
|
@ -120,8 +145,8 @@ Simplifications still apply when `Unknown` is present.
|
||||||
```py
|
```py
|
||||||
from knot_extensions import Unknown
|
from knot_extensions import Unknown
|
||||||
|
|
||||||
def _(u1: str | Unknown | int | object):
|
def _(u1: int | Unknown | bool) -> None:
|
||||||
reveal_type(u1) # revealed: Unknown | object
|
reveal_type(u1) # revealed: int | Unknown
|
||||||
```
|
```
|
||||||
|
|
||||||
## Union of intersections
|
## Union of intersections
|
||||||
|
|
|
@ -631,6 +631,10 @@ impl<'db> Type<'db> {
|
||||||
Self::Dynamic(DynamicType::Unknown)
|
Self::Dynamic(DynamicType::Unknown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn object(db: &'db dyn Db) -> Self {
|
||||||
|
KnownClass::Object.to_instance(db)
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn is_unknown(&self) -> bool {
|
pub const fn is_unknown(&self) -> bool {
|
||||||
matches!(self, Type::Dynamic(DynamicType::Unknown))
|
matches!(self, Type::Dynamic(DynamicType::Unknown))
|
||||||
}
|
}
|
||||||
|
@ -639,6 +643,11 @@ impl<'db> Type<'db> {
|
||||||
matches!(self, Type::Never)
|
matches!(self, Type::Never)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_object(&self, db: &'db dyn Db) -> bool {
|
||||||
|
self.into_instance()
|
||||||
|
.is_some_and(|instance| instance.class.is_object(db))
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn is_todo(&self) -> bool {
|
pub const fn is_todo(&self) -> bool {
|
||||||
matches!(self, Type::Dynamic(DynamicType::Todo(_)))
|
matches!(self, Type::Dynamic(DynamicType::Todo(_)))
|
||||||
}
|
}
|
||||||
|
@ -895,7 +904,7 @@ impl<'db> Type<'db> {
|
||||||
// `object` is the only type that can be known to be a supertype of any intersection,
|
// `object` is the only type that can be known to be a supertype of any intersection,
|
||||||
// even an intersection with no positive elements
|
// even an intersection with no positive elements
|
||||||
(Type::Intersection(_), Type::Instance(InstanceType { class }))
|
(Type::Intersection(_), Type::Instance(InstanceType { class }))
|
||||||
if class.is_known(db, KnownClass::Object) =>
|
if class.is_object(db) =>
|
||||||
{
|
{
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -949,7 +958,7 @@ impl<'db> Type<'db> {
|
||||||
(left, Type::AlwaysTruthy) => left.bool(db).is_always_true(),
|
(left, Type::AlwaysTruthy) => left.bool(db).is_always_true(),
|
||||||
// Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
|
// Currently, the only supertype of `AlwaysFalsy` and `AlwaysTruthy` is the universal set (object instance).
|
||||||
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
|
(Type::AlwaysFalsy | Type::AlwaysTruthy, _) => {
|
||||||
target.is_equivalent_to(db, KnownClass::Object.to_instance(db))
|
target.is_equivalent_to(db, Type::object(db))
|
||||||
}
|
}
|
||||||
|
|
||||||
// All `StringLiteral` types are a subtype of `LiteralString`.
|
// All `StringLiteral` types are a subtype of `LiteralString`.
|
||||||
|
@ -1088,11 +1097,7 @@ impl<'db> Type<'db> {
|
||||||
|
|
||||||
// All types are assignable to `object`.
|
// All types are assignable to `object`.
|
||||||
// TODO this special case might be removable once the below cases are comprehensive
|
// TODO this special case might be removable once the below cases are comprehensive
|
||||||
(_, Type::Instance(InstanceType { class }))
|
(_, Type::Instance(InstanceType { class })) if class.is_object(db) => true,
|
||||||
if class.is_known(db, KnownClass::Object) =>
|
|
||||||
{
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
// A union is assignable to a type T iff every element of the union is assignable to T.
|
// A union is assignable to a type T iff every element of the union is assignable to T.
|
||||||
(Type::Union(union), ty) => union
|
(Type::Union(union), ty) => union
|
||||||
|
@ -1801,7 +1806,7 @@ impl<'db> Type<'db> {
|
||||||
// TODO should be `Callable[[], Literal[True/False]]`
|
// TODO should be `Callable[[], Literal[True/False]]`
|
||||||
todo_type!("`__bool__` for `AlwaysTruthy`/`AlwaysFalsy` Type variants").into()
|
todo_type!("`__bool__` for `AlwaysTruthy`/`AlwaysFalsy` Type variants").into()
|
||||||
}
|
}
|
||||||
_ => KnownClass::Object.to_instance(db).member(db, name),
|
_ => Type::object(db).member(db, name),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3853,6 +3858,11 @@ impl<'db> Class<'db> {
|
||||||
self.known(db) == Some(known_class)
|
self.known(db) == Some(known_class)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if this class represents the builtin class `object`
|
||||||
|
pub fn is_object(self, db: &'db dyn Db) -> bool {
|
||||||
|
self.is_known(db, KnownClass::Object)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return an iterator over the inferred types of this class's *explicit* bases.
|
/// Return an iterator over the inferred types of this class's *explicit* bases.
|
||||||
///
|
///
|
||||||
/// Note that any class (except for `object`) that has no explicit
|
/// Note that any class (except for `object`) that has no explicit
|
||||||
|
|
|
@ -43,6 +43,13 @@ impl<'db> UnionBuilder<'db> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collapse the union to a single type: `object`.
|
||||||
|
fn collapse_to_object(mut self) -> Self {
|
||||||
|
self.elements.clear();
|
||||||
|
self.elements.push(Type::object(self.db));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Adds a type to this union.
|
/// Adds a type to this union.
|
||||||
pub(crate) fn add(mut self, ty: Type<'db>) -> Self {
|
pub(crate) fn add(mut self, ty: Type<'db>) -> Self {
|
||||||
match ty {
|
match ty {
|
||||||
|
@ -53,7 +60,12 @@ impl<'db> UnionBuilder<'db> {
|
||||||
self = self.add(*element);
|
self = self.add(*element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Adding `Never` to a union is a no-op.
|
||||||
Type::Never => {}
|
Type::Never => {}
|
||||||
|
// Adding `object` to a union results in `object`.
|
||||||
|
ty if ty.is_object(self.db) => {
|
||||||
|
return self.collapse_to_object();
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let bool_pair = if let Type::BooleanLiteral(b) = ty {
|
let bool_pair = if let Type::BooleanLiteral(b) = ty {
|
||||||
Some(Type::BooleanLiteral(!b))
|
Some(Type::BooleanLiteral(!b))
|
||||||
|
@ -76,7 +88,10 @@ impl<'db> UnionBuilder<'db> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ty.is_same_gradual_form(*element) || ty.is_subtype_of(self.db, *element) {
|
if ty.is_same_gradual_form(*element)
|
||||||
|
|| ty.is_subtype_of(self.db, *element)
|
||||||
|
|| element.is_object(self.db)
|
||||||
|
{
|
||||||
return self;
|
return self;
|
||||||
} else if element.is_subtype_of(self.db, ty) {
|
} else if element.is_subtype_of(self.db, ty) {
|
||||||
to_remove.push(index);
|
to_remove.push(index);
|
||||||
|
@ -88,9 +103,7 @@ impl<'db> UnionBuilder<'db> {
|
||||||
// `element | ty` must be `object` (object has no other supertypes). This means we can simplify
|
// `element | ty` must be `object` (object has no other supertypes). This means we can simplify
|
||||||
// the whole union to just `object`, since all other potential elements would also be subtypes of
|
// the whole union to just `object`, since all other potential elements would also be subtypes of
|
||||||
// `object`.
|
// `object`.
|
||||||
self.elements.clear();
|
return self.collapse_to_object();
|
||||||
self.elements.push(KnownClass::Object.to_instance(self.db));
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
match to_remove[..] {
|
match to_remove[..] {
|
||||||
|
@ -416,7 +429,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
Type::Never => {
|
Type::Never => {
|
||||||
// Adding ~Never to an intersection is a no-op.
|
// Adding ~Never to an intersection is a no-op.
|
||||||
}
|
}
|
||||||
Type::Instance(instance) if instance.class.is_known(db, KnownClass::Object) => {
|
Type::Instance(instance) if instance.class.is_object(db) => {
|
||||||
// Adding ~object to an intersection results in Never.
|
// Adding ~object to an intersection results in Never.
|
||||||
*self = Self::default();
|
*self = Self::default();
|
||||||
self.positive.insert(Type::Never);
|
self.positive.insert(Type::Never);
|
||||||
|
@ -481,7 +494,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
||||||
|
|
||||||
fn build(mut self, db: &'db dyn Db) -> Type<'db> {
|
fn build(mut self, db: &'db dyn Db) -> Type<'db> {
|
||||||
match (self.positive.len(), self.negative.len()) {
|
match (self.positive.len(), self.negative.len()) {
|
||||||
(0, 0) => KnownClass::Object.to_instance(db),
|
(0, 0) => Type::object(db),
|
||||||
(1, 0) => self.positive[0],
|
(1, 0) => self.positive[0],
|
||||||
_ => {
|
_ => {
|
||||||
self.positive.shrink_to_fit();
|
self.positive.shrink_to_fit();
|
||||||
|
@ -534,7 +547,7 @@ mod tests {
|
||||||
let db = setup_db();
|
let db = setup_db();
|
||||||
|
|
||||||
let intersection = IntersectionBuilder::new(&db).build();
|
let intersection = IntersectionBuilder::new(&db).build();
|
||||||
assert_eq!(intersection, KnownClass::Object.to_instance(&db));
|
assert_eq!(intersection, Type::object(&db));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test_case(Type::BooleanLiteral(true))]
|
#[test_case(Type::BooleanLiteral(true))]
|
||||||
|
@ -548,7 +561,7 @@ mod tests {
|
||||||
// We add t_object in various orders (in first or second position) in
|
// We add t_object in various orders (in first or second position) in
|
||||||
// the tests below to ensure that the boolean simplification eliminates
|
// the tests below to ensure that the boolean simplification eliminates
|
||||||
// everything from the intersection, not just `bool`.
|
// everything from the intersection, not just `bool`.
|
||||||
let t_object = KnownClass::Object.to_instance(&db);
|
let t_object = Type::object(&db);
|
||||||
let t_bool = KnownClass::Bool.to_instance(&db);
|
let t_bool = KnownClass::Bool.to_instance(&db);
|
||||||
|
|
||||||
let ty = IntersectionBuilder::new(&db)
|
let ty = IntersectionBuilder::new(&db)
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::ops::Deref;
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::types::class_base::ClassBase;
|
use crate::types::class_base::ClassBase;
|
||||||
use crate::types::{Class, KnownClass, Type};
|
use crate::types::{Class, Type};
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
|
|
||||||
/// The inferred method resolution order of a given class.
|
/// The inferred method resolution order of a given class.
|
||||||
|
@ -52,9 +52,7 @@ impl<'db> Mro<'db> {
|
||||||
match class_bases {
|
match class_bases {
|
||||||
// `builtins.object` is the special case:
|
// `builtins.object` is the special case:
|
||||||
// the only class in Python that has an MRO with length <2
|
// the only class in Python that has an MRO with length <2
|
||||||
[] if class.is_known(db, KnownClass::Object) => {
|
[] if class.is_object(db) => Ok(Self::from([ClassBase::Class(class)])),
|
||||||
Ok(Self::from([ClassBase::Class(class)]))
|
|
||||||
}
|
|
||||||
|
|
||||||
// All other classes in Python have an MRO with length >=2.
|
// All other classes in Python have an MRO with length >=2.
|
||||||
// Even if a class has no explicit base classes,
|
// Even if a class has no explicit base classes,
|
||||||
|
|
|
@ -152,13 +152,13 @@ fn merge_constraints_or<'db>(
|
||||||
*entry.get_mut() = UnionBuilder::new(db).add(*entry.get()).add(*value).build();
|
*entry.get_mut() = UnionBuilder::new(db).add(*entry.get()).add(*value).build();
|
||||||
}
|
}
|
||||||
Entry::Vacant(entry) => {
|
Entry::Vacant(entry) => {
|
||||||
entry.insert(KnownClass::Object.to_instance(db));
|
entry.insert(Type::object(db));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (key, value) in into.iter_mut() {
|
for (key, value) in into.iter_mut() {
|
||||||
if !from.contains_key(key) {
|
if !from.contains_key(key) {
|
||||||
*value = KnownClass::Object.to_instance(db);
|
*value = Type::object(db);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,7 +329,7 @@ fn union<'db>(db: &'db TestDb, tys: impl IntoIterator<Item = Type<'db>>) -> Type
|
||||||
|
|
||||||
mod stable {
|
mod stable {
|
||||||
use super::union;
|
use super::union;
|
||||||
use crate::types::{KnownClass, Type};
|
use crate::types::Type;
|
||||||
|
|
||||||
// Reflexivity: `T` is equivalent to itself.
|
// Reflexivity: `T` is equivalent to itself.
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
|
@ -419,13 +419,13 @@ mod stable {
|
||||||
// All types should be assignable to `object`
|
// All types should be assignable to `object`
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
all_types_assignable_to_object, db,
|
all_types_assignable_to_object, db,
|
||||||
forall types t. t.is_assignable_to(db, KnownClass::Object.to_instance(db))
|
forall types t. t.is_assignable_to(db, Type::object(db))
|
||||||
);
|
);
|
||||||
|
|
||||||
// And for fully static types, they should also be subtypes of `object`
|
// And for fully static types, they should also be subtypes of `object`
|
||||||
type_property_test!(
|
type_property_test!(
|
||||||
all_fully_static_types_subtype_of_object, db,
|
all_fully_static_types_subtype_of_object, db,
|
||||||
forall types t. t.is_fully_static(db) => t.is_subtype_of(db, KnownClass::Object.to_instance(db))
|
forall types t. t.is_fully_static(db) => t.is_subtype_of(db, Type::object(db))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Never should be assignable to every type
|
// Never should be assignable to every type
|
||||||
|
|
|
@ -411,7 +411,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
Parameter {
|
Parameter {
|
||||||
name: Some(Name::new_static("args")),
|
name: Some(Name::new_static("args")),
|
||||||
annotated_ty: Some(KnownClass::Object.to_instance(&db)),
|
annotated_ty: Some(Type::object(&db)),
|
||||||
kind: ParameterKind::Variadic,
|
kind: ParameterKind::Variadic,
|
||||||
},
|
},
|
||||||
Parameter {
|
Parameter {
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl<'db> SubclassOfType<'db> {
|
||||||
ClassBase::Class(class) => {
|
ClassBase::Class(class) => {
|
||||||
if class.is_final(db) {
|
if class.is_final(db) {
|
||||||
Type::ClassLiteral(ClassLiteralType { class })
|
Type::ClassLiteral(ClassLiteralType { class })
|
||||||
} else if class.is_known(db, KnownClass::Object) {
|
} else if class.is_object(db) {
|
||||||
KnownClass::Type.to_instance(db)
|
KnownClass::Type.to_instance(db)
|
||||||
} else {
|
} else {
|
||||||
Type::SubclassOf(Self { subclass_of })
|
Type::SubclassOf(Self { subclass_of })
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue