From eb6154f7927ba831f06c7adae1cb4c28f8b5ea4f Mon Sep 17 00:00:00 2001 From: Eric Mark Martin Date: Fri, 5 Sep 2025 10:03:10 -0400 Subject: [PATCH] [ty] add doc-comments for some variance stuff (#20189) --- crates/ty_python_semantic/src/types.rs | 9 ++++++++ .../ty_python_semantic/src/types/variance.rs | 23 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index bdb74d90ea..82f81659c7 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6551,6 +6551,7 @@ impl<'db> VarianceInferable<'db> for Type<'db> { } Type::GenericAlias(generic_alias) => generic_alias.variance_of(db, typevar), Type::Callable(callable_type) => callable_type.signatures(db).variance_of(db, typevar), + // A type variable is always covariant in itself. Type::TypeVar(other_typevar) | Type::NonInferableTypeVar(other_typevar) if other_typevar == typevar => { @@ -6560,11 +6561,19 @@ impl<'db> VarianceInferable<'db> for Type<'db> { Type::ProtocolInstance(protocol_instance_type) => { protocol_instance_type.variance_of(db, typevar) } + // unions are covariant in their disjuncts Type::Union(union_type) => union_type .elements(db) .iter() .map(|ty| ty.variance_of(db, typevar)) .collect(), + + // Products are covariant in their conjuncts. For negative + // conjuncts, they're contravariant. To see this, suppose we have + // `B` a subtype of `A`. A value of type `~B` could be some non-`B` + // `A`, and so is not assignable to `~A`. On the other hand, a value + // of type `~A` excludes all `A`s, and thus all `B`s, and so _is_ + // assignable to `~B`. Type::Intersection(intersection_type) => intersection_type .positive(db) .iter() diff --git a/crates/ty_python_semantic/src/types/variance.rs b/crates/ty_python_semantic/src/types/variance.rs index 63d250db56..fb9c87d062 100644 --- a/crates/ty_python_semantic/src/types/variance.rs +++ b/crates/ty_python_semantic/src/types/variance.rs @@ -108,9 +108,30 @@ impl std::iter::FromIterator for TypeVarVariance { } pub(crate) trait VarianceInferable<'db>: Sized { + /// The variance of `typevar` in `self` + /// + /// Generally, one will implement this by traversing any types within `self` + /// in which `typevar` could occur, and calling `variance_of` recursively on + /// them. + /// + /// Sometimes the recursive calls will be in positions where you need to + /// specify a non-covariant polarity. See `with_polarity` for more details. fn variance_of(self, db: &'db dyn Db, typevar: BoundTypeVarInstance<'db>) -> TypeVarVariance; - fn with_polarity(self, polarity: TypeVarVariance) -> WithPolarity { + /// Creates a `VarianceInferable` that applies `polarity` (see + /// `TypeVarVariance::compose`) to the result of variance inference on the + /// underlying value. + /// + /// In some cases, we need to apply a polarity to the recursive call. + /// You can do this with `ty.with_polarity(polarity).variance_of(typevar)`. + /// Generally, this will be whenever the type occurs in argument-position, + /// in which case you will want `TypeVarVariance::Contravariant`, or + /// `TypeVarVariance::Invariant` if the value(s) being annotated is known to + /// be mutable, such as `T` in `list[T]`. See the [typing spec][typing-spec] + /// for more details. + /// + /// [typing-spec]: https://typing.python.org/en/latest/spec/generics.html#variance + fn with_polarity(self, polarity: TypeVarVariance) -> impl VarianceInferable<'db> { WithPolarity { variance_inferable: self, polarity,