Skip to content

Commit fdca2b4

Browse files
carljmAlexWaygood
andauthored
[red-knot] all types are assignable to object (#15332)
## Summary `Type[Any]` should be assignable to `object`. All types should be assignable to `object`. We specifically didn't understand the former; this PR adds a test for it, and a case to ensure that `Type[Any]` is assignable to anything that `type` is assignable to (which includes `object`). This PR also adds a property test that all types are assignable to object. In order to make it pass, I added a special case to check early if we are assigning to `object` and just return `true`. In principle, once we get all the more general cases correct, this special case might be removable. But having the special case for now allows the property test to pass. And we add a property test that all types are subtypes of object. This failed for the case of an intersection with no positive elements (that is, a negation type). This really does need to be a special case for `object`, because there is no other type we can know that a negation type is a subtype of. ## Test Plan Added unit test and property test. --------- Co-authored-by: Alex Waygood <[email protected]>
1 parent 71ad9a2 commit fdca2b4

File tree

3 files changed

+72
-8
lines changed

3 files changed

+72
-8
lines changed

crates/red_knot_python_semantic/src/types.rs

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,14 @@ impl<'db> Type<'db> {
769769
.iter()
770770
.any(|&elem_ty| self.is_subtype_of(db, elem_ty)),
771771

772+
// `object` is the only type that can be known to be a supertype of any intersection,
773+
// even an intersection with no positive elements
774+
(Type::Intersection(_), Type::Instance(InstanceType { class }))
775+
if class.is_known(db, KnownClass::Object) =>
776+
{
777+
true
778+
}
779+
772780
(Type::Intersection(self_intersection), Type::Intersection(target_intersection)) => {
773781
// Check that all target positive values are covered in self positive values
774782
target_intersection
@@ -954,16 +962,32 @@ impl<'db> Type<'db> {
954962
return true;
955963
}
956964
match (self, target) {
965+
// The dynamic type is assignable-to and assignable-from any type.
957966
(Type::Unknown | Type::Any | Type::Todo(_), _) => true,
958967
(_, Type::Unknown | Type::Any | Type::Todo(_)) => true,
968+
969+
// All types are assignable to `object`.
970+
// TODO this special case might be removable once the below cases are comprehensive
971+
(_, Type::Instance(InstanceType { class }))
972+
if class.is_known(db, KnownClass::Object) =>
973+
{
974+
true
975+
}
976+
977+
// A union is assignable to a type T iff every element of the union is assignable to T.
959978
(Type::Union(union), ty) => union
960979
.elements(db)
961980
.iter()
962981
.all(|&elem_ty| elem_ty.is_assignable_to(db, ty)),
982+
983+
// A type T is assignable to a union iff T is assignable to any element of the union.
963984
(ty, Type::Union(union)) => union
964985
.elements(db)
965986
.iter()
966987
.any(|&elem_ty| ty.is_assignable_to(db, elem_ty)),
988+
989+
// A tuple type S is assignable to a tuple type T if their lengths are the same, and
990+
// each element of S is assignable to the corresponding element of T.
967991
(Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => {
968992
let self_elements = self_tuple.elements(db);
969993
let target_elements = target_tuple.elements(db);
@@ -974,28 +998,53 @@ impl<'db> Type<'db> {
974998
},
975999
)
9761000
}
1001+
1002+
// `type[Any]` is assignable to any `type[...]` type, because `type[Any]` can
1003+
// materialize to any `type[...]` type.
9771004
(Type::SubclassOf(subclass_of_ty), Type::SubclassOf(_))
9781005
if subclass_of_ty.is_dynamic() =>
9791006
{
9801007
true
9811008
}
1009+
1010+
// All `type[...]` types are assignable to `type[Any]`, because `type[Any]` can
1011+
// materialize to any `type[...]` type.
1012+
//
1013+
// Every class literal type is also assignable to `type[Any]`, because the class
1014+
// literal type for a class `C` is a subtype of `type[C]`, and `type[C]` is assignable
1015+
// to `type[Any]`.
1016+
(Type::ClassLiteral(_) | Type::SubclassOf(_), Type::SubclassOf(target_subclass_of))
1017+
if target_subclass_of.is_dynamic() =>
1018+
{
1019+
true
1020+
}
1021+
1022+
// `type[Any]` is assignable to any type that `type[object]` is assignable to, because
1023+
// `type[Any]` can materialize to `type[object]`.
1024+
//
1025+
// `type[Any]` is also assignable to any subtype of `type[object]`, because all
1026+
// subtypes of `type[object]` are `type[...]` types (or `Never`), and `type[Any]` can
1027+
// materialize to any `type[...]` type (or to `type[Never]`, which is equivalent to
1028+
// `Never`.)
9821029
(Type::SubclassOf(subclass_of_ty), Type::Instance(_))
9831030
if subclass_of_ty.is_dynamic()
984-
&& target.is_assignable_to(db, KnownClass::Type.to_instance(db)) =>
1031+
&& (KnownClass::Type
1032+
.to_instance(db)
1033+
.is_assignable_to(db, target)
1034+
|| target.is_subtype_of(db, KnownClass::Type.to_instance(db))) =>
9851035
{
9861036
true
9871037
}
1038+
1039+
// Any type that is assignable to `type[object]` is also assignable to `type[Any]`,
1040+
// because `type[Any]` can materialize to `type[object]`.
9881041
(Type::Instance(_), Type::SubclassOf(subclass_of_ty))
9891042
if subclass_of_ty.is_dynamic()
9901043
&& self.is_assignable_to(db, KnownClass::Type.to_instance(db)) =>
9911044
{
9921045
true
9931046
}
994-
(Type::ClassLiteral(_) | Type::SubclassOf(_), Type::SubclassOf(target_subclass_of))
995-
if target_subclass_of.is_dynamic() =>
996-
{
997-
true
998-
}
1047+
9991048
// TODO other types containing gradual forms (e.g. generics containing Any/Unknown)
10001049
_ => self.is_subtype_of(db, target),
10011050
}
@@ -3893,6 +3942,7 @@ pub(crate) mod tests {
38933942
#[test_case(Ty::SubclassOfUnknown, Ty::SubclassOfBuiltinClass("str"))]
38943943
#[test_case(Ty::SubclassOfAny, Ty::AbcInstance("ABCMeta"))]
38953944
#[test_case(Ty::SubclassOfUnknown, Ty::AbcInstance("ABCMeta"))]
3945+
#[test_case(Ty::SubclassOfAny, Ty::BuiltinInstance("object"))]
38963946
fn is_assignable_to(from: Ty, to: Ty) {
38973947
let db = setup_db();
38983948
assert!(from.into_type(&db).is_assignable_to(&db, to.into_type(&db)));
@@ -3976,6 +4026,7 @@ pub(crate) mod tests {
39764026
#[test_case(Ty::Never, Ty::AlwaysTruthy)]
39774027
#[test_case(Ty::Never, Ty::AlwaysFalsy)]
39784028
#[test_case(Ty::BuiltinClassLiteral("bool"), Ty::SubclassOfBuiltinClass("int"))]
4029+
#[test_case(Ty::Intersection{pos: vec![], neg: vec![Ty::LiteralString]}, Ty::BuiltinInstance("object"))]
39794030
fn is_subtype_of(from: Ty, to: Ty) {
39804031
let db = setup_db();
39814032
assert!(from.into_type(&db).is_subtype_of(&db, to.into_type(&db)));

crates/red_knot_python_semantic/src/types/property_tests.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ macro_rules! type_property_test {
220220
}
221221

222222
mod stable {
223+
use super::KnownClass;
224+
223225
// `T` is equivalent to itself.
224226
type_property_test!(
225227
equivalent_to_is_reflexive, db,
@@ -285,6 +287,18 @@ mod stable {
285287
non_fully_static_types_do_not_participate_in_subtyping, db,
286288
forall types s, t. !s.is_fully_static(db) => !s.is_subtype_of(db, t) && !t.is_subtype_of(db, s)
287289
);
290+
291+
// All types should be assignable to `object`
292+
type_property_test!(
293+
all_types_assignable_to_object, db,
294+
forall types t. t.is_assignable_to(db, KnownClass::Object.to_instance(db))
295+
);
296+
297+
// And for fully static types, they should also be subtypes of `object`
298+
type_property_test!(
299+
all_fully_static_types_subtype_of_object, db,
300+
forall types t. t.is_fully_static(db) => t.is_subtype_of(db, KnownClass::Object.to_instance(db))
301+
);
288302
}
289303

290304
/// This module contains property tests that currently lead to many false positives.

crates/ruff_benchmark/benches/red_knot.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
3333
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:104:14 Name `char` used when possibly not defined",
3434
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:115:14 Name `char` used when possibly not defined",
3535
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:126:12 Name `char` used when possibly not defined",
36-
// We don't handle intersections in `is_assignable_to` yet
37-
"error[lint:invalid-argument-type] /src/tomllib/_parser.py:211:31 Object of type `Unknown & object | @Todo` cannot be assigned to parameter 1 (`obj`) of function `isinstance`; expected type `object`",
3836
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:348:20 Name `nest` used when possibly not defined",
3937
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:353:5 Name `nest` used when possibly not defined",
4038
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:453:24 Name `nest` used when possibly not defined",
@@ -44,6 +42,7 @@ static EXPECTED_DIAGNOSTICS: &[&str] = &[
4442
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:573:12 Name `char` used when possibly not defined",
4543
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:579:12 Name `char` used when possibly not defined",
4644
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:580:63 Name `char` used when possibly not defined",
45+
// We don't handle intersections in `is_assignable_to` yet
4746
"error[lint:invalid-argument-type] /src/tomllib/_parser.py:626:46 Object of type `@Todo & ~AlwaysFalsy` cannot be assigned to parameter 1 (`match`) of function `match_to_datetime`; expected type `Match`",
4847
"warning[lint:possibly-unresolved-reference] /src/tomllib/_parser.py:629:38 Name `datetime_obj` used when possibly not defined",
4948
"error[lint:invalid-argument-type] /src/tomllib/_parser.py:632:58 Object of type `@Todo & ~AlwaysFalsy` cannot be assigned to parameter 1 (`match`) of function `match_to_localtime`; expected type `Match`",

0 commit comments

Comments
 (0)