Skip to content

Commit e0adf94

Browse files
committed
Revert back to S+ <: T- definition of gradual subtyping
1 parent 6645ddb commit e0adf94

File tree

6 files changed

+183
-97
lines changed

6 files changed

+183
-97
lines changed

crates/ty_python_semantic/resources/mdtest/generics/pep695/variables.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def bounded_by_gradual[T: Any](t: T) -> None:
181181
static_assert(is_assignable_to(T, Sub))
182182
static_assert(not is_assignable_to(Sub, T))
183183

184-
static_assert(is_subtype_of(T, Any))
184+
static_assert(not is_subtype_of(T, Any))
185185
static_assert(not is_subtype_of(Any, T))
186186
static_assert(not is_subtype_of(T, Super))
187187
static_assert(not is_subtype_of(Super, T))
@@ -275,7 +275,7 @@ def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
275275
static_assert(not is_subtype_of(T, Sub))
276276
static_assert(not is_subtype_of(T, Unrelated))
277277
static_assert(not is_subtype_of(T, Any))
278-
static_assert(is_subtype_of(T, Super | Any))
278+
static_assert(not is_subtype_of(T, Super | Any))
279279
static_assert(not is_subtype_of(T, Super | Unrelated))
280280
static_assert(not is_subtype_of(Super, T))
281281
static_assert(not is_subtype_of(Base, T))
@@ -285,7 +285,7 @@ def constrained_by_gradual[T: (Base, Any)](t: T) -> None:
285285
static_assert(not is_subtype_of(Base | Any, T))
286286
static_assert(not is_subtype_of(Super | Unrelated, T))
287287
static_assert(not is_subtype_of(Intersection[Base, Unrelated], T))
288-
static_assert(is_subtype_of(Intersection[Base, Any], T))
288+
static_assert(not is_subtype_of(Intersection[Base, Any], T))
289289
```
290290

291291
Two distinct fully static typevars are not subtypes of each other, even if they have the same

crates/ty_python_semantic/resources/mdtest/type_properties/is_subtype_of.md

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ Its subtyping follows the general rule for subtyping of gradual types.
329329
from typing import Any
330330
from ty_extensions import static_assert, is_subtype_of
331331

332-
static_assert(is_subtype_of(tuple[Any, ...], tuple[Any, ...]))
332+
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any, ...]))
333333
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any]))
334334
static_assert(not is_subtype_of(tuple[Any, ...], tuple[Any, Any]))
335335
static_assert(not is_subtype_of(tuple[Any, ...], tuple[int, ...]))
@@ -340,7 +340,7 @@ static_assert(not is_subtype_of(tuple[Any, ...], tuple[int, int]))
340340
Same applies when `tuple[Any, ...]` is unpacked into a mixed tuple.
341341

342342
```py
343-
static_assert(is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[Any, ...]]))
343+
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, *tuple[Any, ...]]))
344344
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any, ...]))
345345
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any]))
346346
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[Any, Any]))
@@ -349,7 +349,7 @@ static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, ...]))
349349
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int]))
350350
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...]], tuple[int, int]))
351351

352-
static_assert(is_subtype_of(tuple[*tuple[Any, ...], int], tuple[*tuple[Any, ...], int]))
352+
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[*tuple[Any, ...], int]))
353353
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any, ...]))
354354
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any]))
355355
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[Any, Any]))
@@ -358,7 +358,7 @@ static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int, ...]))
358358
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int]))
359359
static_assert(not is_subtype_of(tuple[*tuple[Any, ...], int], tuple[int, int]))
360360

361-
static_assert(is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[Any, ...], int]))
361+
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[int, *tuple[Any, ...], int]))
362362
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any, ...]))
363363
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any]))
364364
static_assert(not is_subtype_of(tuple[int, *tuple[Any, ...], int], tuple[Any, Any]))
@@ -594,13 +594,13 @@ static_assert(is_subtype_of(TypeIs[str], int))
594594
from ty_extensions import is_equivalent_to, is_subtype_of, static_assert
595595
from typing_extensions import TypeGuard, TypeIs
596596

597-
static_assert(is_subtype_of(TypeGuard[int], TypeGuard[int]))
598-
static_assert(is_subtype_of(TypeGuard[bool], TypeGuard[int]))
597+
# TODO: TypeGuard
598+
# static_assert(is_subtype_of(TypeGuard[int], TypeGuard[int]))
599+
# static_assert(is_subtype_of(TypeGuard[bool], TypeGuard[int]))
599600
static_assert(is_subtype_of(TypeIs[int], TypeIs[int]))
600601
static_assert(is_subtype_of(TypeIs[int], TypeIs[int]))
601602

602-
# TODO: TypeGuard
603-
static_assert(not is_subtype_of(TypeGuard[int], TypeGuard[bool])) # error: [static-assert-error]
603+
static_assert(not is_subtype_of(TypeGuard[int], TypeGuard[bool]))
604604
static_assert(not is_subtype_of(TypeIs[bool], TypeIs[int]))
605605
static_assert(not is_subtype_of(TypeIs[int], TypeIs[bool]))
606606
```
@@ -763,49 +763,44 @@ static_assert(is_subtype_of(LiteralBase | LiteralUnrelated, object))
763763

764764
## Non-fully-static types
765765

766-
A non-fully-static type `A` can be considered a subtype of another type `B` if all possible
767-
materializations of `A` are a subtype of some possible materialization of `B`, and all possible
768-
materializations of `B` are a supertype of some possible materialization of `A`.
766+
A non-fully-static type can be considered a subtype of another type if all possible materializations
767+
of the first type represent sets of values that are a subset of every possible set of values
768+
represented by a materialization of the second type.
769769

770770
```py
771771
from ty_extensions import Unknown, is_subtype_of, static_assert, Intersection
772772
from typing_extensions import Any
773773

774-
static_assert(is_subtype_of(Any, Any))
774+
static_assert(not is_subtype_of(Any, Any))
775775
static_assert(not is_subtype_of(Any, int))
776776
static_assert(not is_subtype_of(int, Any))
777777
static_assert(is_subtype_of(Any, object))
778778
static_assert(not is_subtype_of(object, Any))
779779

780780
static_assert(is_subtype_of(int, Any | int))
781-
static_assert(is_subtype_of(Any, Any | int))
782781
static_assert(is_subtype_of(Intersection[Any, int], int))
783782
static_assert(not is_subtype_of(tuple[int, int], tuple[int, Any]))
784783
```
785784

786785
The same for `Unknown`:
787786

788787
```py
789-
static_assert(is_subtype_of(Unknown, Unknown))
790-
static_assert(is_subtype_of(Any, Unknown))
791-
static_assert(is_subtype_of(Unknown, Any))
788+
static_assert(not is_subtype_of(Unknown, Unknown))
792789
static_assert(not is_subtype_of(Unknown, int))
793790
static_assert(not is_subtype_of(int, Unknown))
794791
static_assert(is_subtype_of(Unknown, object))
795792
static_assert(not is_subtype_of(object, Unknown))
796793

797794
static_assert(is_subtype_of(int, Unknown | int))
798-
static_assert(is_subtype_of(Unknown, Unknown | int))
799795
static_assert(is_subtype_of(Intersection[Unknown, int], int))
800796
static_assert(not is_subtype_of(tuple[int, int], tuple[int, Unknown]))
801797
```
802798

803799
Instances of classes that inherit `Any` are not subtypes of some other arbitrary class, because the
804800
`Any` they inherit from could materialize to something that is not a subclass of that class.
805801

806-
They are also not subtypes of `Any`, because some materializations of `Any` (for example, a
807-
materialization into some unrelated final type) are not a super-type of any materialization of a
808-
specific class inheriting `Any`.
802+
Similarly, they are not subtypes of `Any`, because there are possible materializations that would
803+
not satisfy the subtype relation.
809804

810805
They are subtypes of `object`.
811806

@@ -824,7 +819,7 @@ static_assert(is_subtype_of(InheritsAny, object))
824819
Similar for subclass-of types:
825820

826821
```py
827-
static_assert(is_subtype_of(type[Any], type[Any]))
822+
static_assert(not is_subtype_of(type[Any], type[Any]))
828823
static_assert(not is_subtype_of(type[object], type[Any]))
829824
static_assert(not is_subtype_of(type[Any], type[Arbitrary]))
830825
static_assert(is_subtype_of(type[Any], type[object]))

crates/ty_python_semantic/src/types.rs

Lines changed: 73 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,45 +1165,93 @@ impl<'db> Type<'db> {
11651165
}
11661166
}
11671167

1168-
/// Return true if this type is a subtype of type `target`.
1168+
/// Return true if this is a type that is always fully static (has no dynamic part; represents
1169+
/// a single set of possible values.)
1170+
fn must_be_fully_static(self) -> bool {
1171+
match self {
1172+
Type::Never
1173+
| Type::FunctionLiteral(..)
1174+
| Type::BoundMethod(_)
1175+
| Type::WrapperDescriptor(_)
1176+
| Type::MethodWrapper(_)
1177+
| Type::DataclassDecorator(_)
1178+
| Type::DataclassTransformer(_)
1179+
| Type::ModuleLiteral(..)
1180+
| Type::IntLiteral(_)
1181+
| Type::BooleanLiteral(_)
1182+
| Type::StringLiteral(_)
1183+
| Type::LiteralString
1184+
| Type::BytesLiteral(_)
1185+
| Type::SpecialForm(_)
1186+
| Type::KnownInstance(_)
1187+
| Type::AlwaysFalsy
1188+
| Type::AlwaysTruthy
1189+
| Type::PropertyInstance(_) => true,
1190+
Type::ClassLiteral(_) | Type::GenericAlias(_) | Type::NominalInstance(_) => {
1191+
// TODO: This is wrong because the type might inherit Any
1192+
true
1193+
}
1194+
_ => false,
1195+
}
1196+
}
1197+
1198+
/// Return true if this type is a [subtype of] type `target`.
1199+
///
1200+
/// For fully static types, this means that the set of objects represented by `self` is a
1201+
/// subset of the objects represented by `target`.
11691202
///
1170-
/// We define subtyping somewhat more broadly than [the typing spec], which defines a
1171-
/// strict subtyping relation that applies only to fully-static types. Our subtyping relation
1172-
/// is intended to be used in simplifying union and intersection types, and thus aims to uphold
1173-
/// the invariant that if `A` is a subtype of `B`, then `A | B` is equivalent to `B`, and `A &
1174-
/// B` is equivalent to `A`. Thus, we consider `Any` to be a subtype of `Any` and `object`,
1175-
/// for example.
1203+
/// For gradual types, it means that the union of all possible sets of values represented by
1204+
/// `self` (the "top materialization" of `self`) is a subtype of the intersection of all
1205+
/// possible sets of values represented by `target` (the "bottom materialization" of
1206+
/// `target`). In other words, for all possiblepairs of materializations `self'` and
1207+
/// `target'`, `self'` is always a subtype of `target'`.
11761208
///
1177-
/// The general principle we aim for is that `A` is a subtype of `B` if all materializations of
1178-
/// `A` are a subtype of some materialization of `B`, and all materializations of `B` are a
1179-
/// supertype of some materialization of `A`.
1209+
/// Note that this latter expansion of the subtyping relation to non-fully-static types is not
1210+
/// described in the typing spec, but the primary use of the subtyping relation is for
1211+
/// simplifying unions and intersections, and this expansion to gradual types is sound and
1212+
/// allows us to better simplify many unions and intersections. This definition does mean the
1213+
/// subtyping relation is not reflexive for non-fully-static types (e.g. `Any` is not a subtype
1214+
/// of `Any`).
11801215
///
1181-
/// [the typing spec]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
1216+
/// [subtype of]: https://typing.python.org/en/latest/spec/concepts.html#subtype-supertype-and-type-equivalence
1217+
///
1218+
/// There would be an even more general definition of subtyping for gradual types, allowing a
1219+
/// type `S` to be a subtype of a type `T` if the top materialization of `S` (`S+`) is a
1220+
/// subtype of `T+`, and the bottom materialization of `S` (`S-`) is a subtype of `T-`. This
1221+
/// definition is attractive in that it would restore reflexivity of subtyping for all types,
1222+
/// and would mean that gradual equivalence of `S` and `T` could be defined simply as `S <: T
1223+
/// && T <: S`. It would also be sound, in that simplifying unions or intersections according
1224+
/// to this definition of subtyping would still result in an equivalent type.
1225+
///
1226+
/// Unfortunately using this definition would break transitivity of subtyping when
1227+
/// both nominal and structural types are involved, because Liskov enforcement for nominal
1228+
/// types is based on assignability, so we can have class `A` with method `def meth() -> Any`
1229+
/// and a subclass `B(A)` with method `def meth() -> int`. In this case, `A` would be a subtype
1230+
/// of a protocol `P` with method `def meth() -> str`, but `B` would not be a subtype of `P`,
1231+
/// and yet `B` is (by nominal subtyping) a subtype of `A`, so we would have `B <: A` and `A <:
1232+
/// P`, but not `B <: P`. Losing transitivity of subtyping is not tenable (it makes union and
1233+
/// intersection simplification dependent on the order in which elements are added), so we do
1234+
/// not use this more general definition of subtyping.
11821235
pub(crate) fn is_subtype_of(self, db: &'db dyn Db, target: Type<'db>) -> bool {
11831236
self.has_relation_to(db, target, TypeRelation::Subtyping)
11841237
}
11851238

11861239
/// Return true if this type is [assignable to] type `target`.
11871240
///
1188-
/// In contrast to subtyping, `A` is assignable to `B` if there are any possible
1189-
/// materializations `A'` and `B'` such that `A'` is a subtype of `B'`.
1190-
///
1191-
/// TODO: We may need to add a restriction that this "possible materialization" cannot be
1192-
/// `Never`.
1193-
///
11941241
/// [assignable to]: https://typing.python.org/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
11951242
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
11961243
self.has_relation_to(db, target, TypeRelation::Assignability)
11971244
}
11981245

11991246
fn has_relation_to(self, db: &'db dyn Db, target: Type<'db>, relation: TypeRelation) -> bool {
1200-
// If two types are the same type, they are both subtypes of each other and assignable to
1201-
// each other.
1202-
if self == target {
1247+
// If two types that must be fully static are the same type, they are both subtypes of
1248+
// each other and assignable to each other.
1249+
if self.must_be_fully_static() && target.must_be_fully_static() && self == target {
12031250
return true;
12041251
}
1205-
// If two types are equivalent, they are subtypes of and assignable to each other.
1206-
if self.is_equivalent_to(db, target) {
1252+
// If two types are equivalent, they are assignable to each other. (Equivalence does
1253+
// not imply subtyping, since it may be gradual equivalence.)
1254+
if relation.is_assignability() && self.is_equivalent_to(db, target) {
12071255
return true;
12081256
}
12091257

@@ -1215,11 +1263,9 @@ impl<'db> Type<'db> {
12151263
// It is a subtype of all other types.
12161264
(Type::Never, _) => true,
12171265

1218-
// The dynamic type is always assignable to and a subtype of itself.
1219-
(Type::Dynamic(_), Type::Dynamic(_)) => true,
1220-
1221-
// The dynamic type is always assignable to or from any other type.
1222-
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) if relation.is_assignability() => true,
1266+
// Dynamic is only a subtype of `object` and only a supertype of `Never`; both were
1267+
// handled above. It's always assignable, though.
1268+
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => relation.is_assignability(),
12231269

12241270
// In general, a TypeVar `T` is not a subtype of a type `S` unless one of the two conditions is satisfied:
12251271
// 1. `T` is a bound TypeVar and `T`'s upper bound is a subtype of `S`.
@@ -1319,10 +1365,6 @@ impl<'db> Type<'db> {
13191365
// be specialized to `Never`.)
13201366
(_, Type::TypeVar(_)) => false,
13211367

1322-
// Other than `object` or `Never` or unions or intersections, all handled above,
1323-
// the dynamic type is never a subtype or supertype of any other type.
1324-
(Type::Dynamic(_), _) | (_, Type::Dynamic(_)) => false,
1325-
13261368
// Note that the definition of `Type::AlwaysFalsy` depends on the return value of `__bool__`.
13271369
// If `__bool__` always returns True or False, it can be treated as a subtype of `AlwaysTruthy` or `AlwaysFalsy`, respectively.
13281370
(left, Type::AlwaysFalsy) => left.bool(db).is_always_false(),

crates/ty_python_semantic/src/types/builder.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,9 @@ impl<'db> UnionBuilder<'db> {
369369
return;
370370
}
371371

372-
if ty.is_subtype_of(self.db, element_type) {
372+
if ty.is_equivalent_to(self.db, element_type)
373+
|| ty.is_subtype_of(self.db, element_type)
374+
{
373375
return;
374376
} else if element_type.is_subtype_of(self.db, ty) {
375377
to_remove.push(index);
@@ -671,7 +673,9 @@ impl<'db> InnerIntersectionBuilder<'db> {
671673
let mut to_remove = SmallVec::<[usize; 1]>::new();
672674
for (index, existing_positive) in self.positive.iter().enumerate() {
673675
// S & T = S if S <: T
674-
if existing_positive.is_subtype_of(db, new_positive) {
676+
if existing_positive.is_subtype_of(db, new_positive)
677+
|| existing_positive.is_equivalent_to(db, new_positive)
678+
{
675679
return;
676680
}
677681
// same rule, reverse order
@@ -766,7 +770,9 @@ impl<'db> InnerIntersectionBuilder<'db> {
766770
let mut to_remove = SmallVec::<[usize; 1]>::new();
767771
for (index, existing_negative) in self.negative.iter().enumerate() {
768772
// ~S & ~T = ~T if S <: T
769-
if existing_negative.is_subtype_of(db, new_negative) {
773+
if existing_negative.is_subtype_of(db, new_negative)
774+
|| existing_negative.is_equivalent_to(db, new_negative)
775+
{
770776
to_remove.push(index);
771777
}
772778
// same rule, reverse order

crates/ty_python_semantic/src/types/property_tests.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,19 @@ macro_rules! type_property_test {
5050
$property
5151
}
5252
};
53+
($test_name:ident, $db:ident, forall fully_static_types $($types:ident),+ . $property:expr) => {
54+
#[quickcheck_macros::quickcheck]
55+
#[ignore]
56+
fn $test_name($($types: crate::types::property_tests::type_generation::FullyStaticTy),+) -> bool {
57+
let $db = &crate::types::property_tests::setup::get_cached_db();
58+
$(let $types = $types.into_type($db);)+
59+
60+
$property
61+
}
62+
};
5363
// A property test with a logical implication.
54-
($name:ident, $db:ident, forall types $($types:ident),+ . $premise:expr => $conclusion:expr) => {
55-
type_property_test!($name, $db, forall types $($types),+ . !($premise) || ($conclusion));
64+
($name:ident, $db:ident, forall $typekind:ident $($types:ident),+ . $premise:expr => $conclusion:expr) => {
65+
type_property_test!($name, $db, forall $typekind $($types),+ . !($premise) || ($conclusion));
5666
};
5767
}
5868

@@ -90,12 +100,6 @@ mod stable {
90100
forall types s, t. s.is_subtype_of(db, t) && t.is_subtype_of(db, s) => s.is_equivalent_to(db, t)
91101
);
92102

93-
// Equivalence implies subtyping.
94-
type_property_test!(
95-
equivalence_implies_subtype, db,
96-
forall types s, t. s.is_equivalent_to(db, t) => s.is_subtype_of(db, t) && t.is_subtype_of(db, s)
97-
);
98-
99103
// `T` is not disjoint from itself, unless `T` is `Never`.
100104
type_property_test!(
101105
disjoint_from_is_irreflexive, db,
@@ -169,16 +173,16 @@ mod stable {
169173
forall types s, t. s.is_assignable_to(db, union(db, [s, t])) && t.is_assignable_to(db, union(db, [s, t]))
170174
);
171175

172-
// A type `T` is a subtype of itself.
176+
// A fully static type `T` is a subtype of itself.
173177
type_property_test!(
174-
subtype_of_is_reflexive, db,
175-
forall types t. t.is_subtype_of(db, t)
178+
subtype_of_is_reflexive_for_fully_static_types, db,
179+
forall fully_static_types t. t.is_subtype_of(db, t)
176180
);
177181

178-
// For any two types, each type in the pair must be a subtype of their union.
182+
// For any two fully static types, each type in the pair must be a subtype of their union.
179183
type_property_test!(
180-
all_type_pairs_are_subtype_of_their_union, db,
181-
forall types s, t. s.is_subtype_of(db, union(db, [s, t])) && t.is_subtype_of(db, union(db, [s, t]))
184+
all_fully_static_type_pairs_are_subtype_of_their_union, db,
185+
forall fully_static_types s, t. s.is_subtype_of(db, union(db, [s, t])) && t.is_subtype_of(db, union(db, [s, t]))
182186
);
183187
}
184188

0 commit comments

Comments
 (0)