Compare commits

...

5 Commits

Author SHA1 Message Date
Alex Waygood
c95cdb3d1d Add regression test and comment for GenericContext::from_type_params 2025-08-15 11:15:42 +01:00
Alex Waygood
7c34ed3374 fix panic 2025-08-14 22:01:21 +01:00
Alex Waygood
5b0a93b3c0 fix memory usage reports 2025-08-14 22:01:21 +01:00
Alex Waygood
cfc46acc46 Do the hard things (with some help from Doug!) 2025-08-14 22:01:21 +01:00
Alex Waygood
671f1358fb Do the easy functions 2025-08-14 22:01:21 +01:00
8 changed files with 360 additions and 161 deletions

View File

@@ -617,5 +617,34 @@ class C[T](C): ...
class D[T](D[int]): ...
```
## Tuple as a PEP-695 generic class
Our special handling for `tuple` does not break if `tuple` is defined as a PEP-695 generic class in
typeshed:
```toml
[environment]
python-version = "3.12"
typeshed = "/typeshed"
```
`/typeshed/stdlib/builtins.pyi`:
```pyi
class tuple[T]: ...
```
`/typeshed/stdlib/typing_extensions.pyi`:
```pyi
def reveal_type(obj, /): ...
```
`main.py`:
```py
reveal_type((1, 2, 3)) # revealed: tuple[Literal[1], Literal[2], Literal[3]]
```
[crtp]: https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern
[f-bound]: https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification

View File

@@ -133,6 +133,17 @@ def _[T](x: A | B):
reveal_type(x) # revealed: A[int] | B
```
## Narrowing for tuple
An early version of <https://github.com/astral-sh/ruff/pull/19920> caused us to crash on this:
```py
def _(val):
if type(val) is tuple:
# TODO: better would be `Unknown & tuple[object, ...]`
reveal_type(val) # revealed: Unknown & tuple[Unknown, ...]
```
## Limitations
```py

View File

@@ -1266,7 +1266,7 @@ impl<'db> ClassLiteral<'db> {
class_def_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
let definition = index.expect_single_definition(class_def_node);
GenericContext::from_type_params(db, index, definition, type_params)
GenericContext::from_type_params(db, index, definition, type_params, self.known(db))
})
}
@@ -1290,6 +1290,7 @@ impl<'db> ClassLiteral<'db> {
.iter()
.copied()
.filter(|ty| matches!(ty, Type::GenericAlias(_))),
self.known(db),
)
}
@@ -1334,8 +1335,7 @@ impl<'db> ClassLiteral<'db> {
specialization: Option<Specialization<'db>>,
) -> ClassType<'db> {
self.apply_specialization(db, |generic_context| {
specialization
.unwrap_or_else(|| generic_context.default_specialization(db, self.known(db)))
specialization.unwrap_or_else(|| generic_context.default_specialization(db))
})
}
@@ -1344,7 +1344,7 @@ impl<'db> ClassLiteral<'db> {
/// applies the default specialization to the class's typevars.
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> ClassType<'db> {
self.apply_specialization(db, |generic_context| {
generic_context.default_specialization(db, self.known(db))
generic_context.default_specialization(db)
})
}

View File

@@ -454,7 +454,6 @@ impl Display for DisplayGenericContext<'_> {
let variables = self.generic_context.variables(self.db);
let non_implicit_variables: Vec<_> = variables
.iter()
.filter(|bound_typevar| !bound_typevar.typevar(self.db).is_implicit(self.db))
.collect();

View File

@@ -340,7 +340,7 @@ impl<'db> OverloadLiteral<'db> {
let definition = self.definition(db);
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, definition, type_params)
GenericContext::from_type_params(db, index, definition, type_params, None)
});
let index = semantic_index(db, scope.file(db));

View File

@@ -1,5 +1,6 @@
use std::borrow::Cow;
use itertools::Either;
use ruff_db::parsed::ParsedModuleRef;
use ruff_python_ast as ast;
use rustc_hash::FxHashMap;
@@ -12,7 +13,7 @@ use crate::types::class_base::ClassBase;
use crate::types::infer::infer_definition_types;
use crate::types::instance::{Protocol, ProtocolInstanceType};
use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, HasRelationToVisitor, KnownClass,
KnownInstanceType, NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeTransformer,
@@ -82,6 +83,14 @@ pub(crate) fn bind_typevar<'db>(
})
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum GenericContextInner<'db> {
Tuple {
single_typevar: BoundTypeVarInstance<'db>,
},
NonTuple(FxOrderSet<BoundTypeVarInstance<'db>>),
}
/// A list of formal type variables for a generic function, class, or type alias.
///
/// TODO: Handle nested generic contexts better, with actual parent links to the lexically
@@ -94,7 +103,7 @@ pub(crate) fn bind_typevar<'db>(
#[derive(PartialOrd, Ord)]
pub struct GenericContext<'db> {
#[returns(ref)]
pub(crate) variables: FxOrderSet<BoundTypeVarInstance<'db>>,
pub(crate) inner: GenericContextInner<'db>,
}
pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
@@ -103,7 +112,7 @@ pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?S
visitor: &V,
) {
for bound_typevar in context.variables(db) {
visitor.visit_bound_type_var_type(db, *bound_typevar);
visitor.visit_bound_type_var_type(db, bound_typevar);
}
}
@@ -111,12 +120,25 @@ pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?S
impl get_size2::GetSize for GenericContext<'_> {}
impl<'db> GenericContext<'db> {
pub(crate) fn variables(
self,
db: &'db dyn Db,
) -> impl ExactSizeIterator<Item = BoundTypeVarInstance<'db>> {
match self.inner(db) {
GenericContextInner::Tuple { single_typevar } => {
Either::Left(std::iter::once(*single_typevar))
}
GenericContextInner::NonTuple(variables) => Either::Right(variables.iter().copied()),
}
}
/// Creates a generic context from a list of PEP-695 type parameters.
pub(crate) fn from_type_params(
db: &'db dyn Db,
index: &'db SemanticIndex<'db>,
binding_context: Definition<'db>,
type_params_node: &ast::TypeParams,
known_class: Option<KnownClass>,
) -> Self {
let variables: FxOrderSet<_> = type_params_node
.iter()
@@ -124,7 +146,29 @@ impl<'db> GenericContext<'db> {
Self::variable_from_type_param(db, index, binding_context, type_param)
})
.collect();
Self::new(db, variables)
match known_class {
// As of 15/08/2025, this branch is never taken, since `tuple` in typeshed
// does not have any PEP-695 type parameters. However, it will presumably
// at some point have its stub definition rewritten to use PEP-695 type parameters,
// and a custom typeshed could also reasonably define `tuple` in `builtins.pyi`
// with PEP-695 type parameters. (We have a regression test for this in
// `generics/pep695/classes.md`.)
Some(KnownClass::Tuple) => {
assert_eq!(
variables.len(),
1,
"Tuple should always have exactly one typevar"
);
Self::new(
db,
GenericContextInner::Tuple {
single_typevar: variables[0],
},
)
}
_ => Self::new(db, GenericContextInner::NonTuple(variables)),
}
}
fn variable_from_type_param(
@@ -174,7 +218,7 @@ impl<'db> GenericContext<'db> {
if variables.is_empty() {
return None;
}
Some(Self::new(db, variables))
Some(Self::new(db, GenericContextInner::NonTuple(variables)))
}
/// Creates a generic context from the legacy `TypeVar`s that appear in class's base class
@@ -182,6 +226,7 @@ impl<'db> GenericContext<'db> {
pub(crate) fn from_base_classes(
db: &'db dyn Db,
bases: impl Iterator<Item = Type<'db>>,
known_class: Option<KnownClass>,
) -> Option<Self> {
let mut variables = FxOrderSet::default();
for base in bases {
@@ -190,7 +235,23 @@ impl<'db> GenericContext<'db> {
if variables.is_empty() {
return None;
}
Some(Self::new(db, variables))
let context = match known_class {
Some(KnownClass::Tuple) => {
assert_eq!(
variables.len(),
1,
"Tuple should always have exactly one typevar"
);
Self::new(
db,
GenericContextInner::Tuple {
single_typevar: variables[0],
},
)
}
_ => Self::new(db, GenericContextInner::NonTuple(variables)),
};
Some(context)
}
pub(crate) fn len(self, db: &'db dyn Db) -> usize {
@@ -200,8 +261,7 @@ impl<'db> GenericContext<'db> {
pub(crate) fn signature(self, db: &'db dyn Db) -> Signature<'db> {
let parameters = Parameters::new(
self.variables(db)
.iter()
.map(|typevar| Self::parameter_from_typevar(db, *typevar)),
.map(|typevar| Self::parameter_from_typevar(db, typevar)),
);
Signature::new(parameters, None)
}
@@ -231,50 +291,54 @@ impl<'db> GenericContext<'db> {
parameter
}
pub(crate) fn default_specialization(
self,
db: &'db dyn Db,
known_class: Option<KnownClass>,
) -> Specialization<'db> {
let partial = self.specialize_partial(db, &vec![None; self.variables(db).len()]);
if known_class == Some(KnownClass::Tuple) {
Specialization::new(
db,
self,
partial.types(db),
Some(TupleType::homogeneous(db, Type::unknown())),
)
} else {
partial
}
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
self.specialize_partial(db, &vec![None; self.variables(db).len()])
}
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
let types = self
.variables(db)
.iter()
.map(|typevar| Type::TypeVar(*typevar))
.collect();
let types = self.variables(db).map(Type::TypeVar).collect();
self.specialize(db, types)
}
pub(crate) fn unknown_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
let types = vec![Type::unknown(); self.variables(db).len()];
self.specialize(db, types.into())
self.specialize(db, types.into_boxed_slice())
}
/// Returns a tuple type of the typevars introduced by this generic context.
pub(crate) fn as_tuple(self, db: &'db dyn Db) -> Type<'db> {
Type::heterogeneous_tuple(
db,
self.variables(db)
.iter()
.map(|typevar| Type::TypeVar(*typevar)),
)
Type::heterogeneous_tuple(db, self.variables(db).map(Type::TypeVar))
}
pub(crate) fn is_subset_of(self, db: &'db dyn Db, other: GenericContext<'db>) -> bool {
self.variables(db).is_subset(other.variables(db))
match (self.inner(db), other.inner(db)) {
(GenericContextInner::NonTuple(left), GenericContextInner::NonTuple(right)) => {
left.is_subset(right)
}
(
GenericContextInner::Tuple {
single_typevar: left,
},
GenericContextInner::NonTuple(right),
) => right.contains(left),
(
GenericContextInner::NonTuple(left),
GenericContextInner::Tuple {
single_typevar: right,
},
) => left.len() == 1 && left[0] == *right,
(
GenericContextInner::Tuple {
single_typevar: left,
},
GenericContextInner::Tuple {
single_typevar: right,
},
) => left == right,
}
}
pub(crate) fn binds_typevar(
@@ -283,9 +347,7 @@ impl<'db> GenericContext<'db> {
typevar: TypeVarInstance<'db>,
) -> Option<BoundTypeVarInstance<'db>> {
self.variables(db)
.iter()
.find(|self_bound_typevar| self_bound_typevar.typevar(db) == typevar)
.copied()
}
/// Creates a specialization of this generic context. Panics if the length of `types` does not
@@ -297,18 +359,30 @@ impl<'db> GenericContext<'db> {
db: &'db dyn Db,
types: Box<[Type<'db>]>,
) -> Specialization<'db> {
assert!(self.variables(db).len() == types.len());
Specialization::new(db, self, types, None)
match self.inner(db) {
GenericContextInner::Tuple { .. } => {
assert_eq!(types.len(), 1);
let tuple = TupleType::homogeneous(db, types[0]);
Specialization::new(db, self, SpecializationInner::Tuple(tuple))
}
GenericContextInner::NonTuple(variables) => {
assert_eq!(variables.len(), types.len());
Specialization::new(db, self, SpecializationInner::NonTuple(types))
}
}
}
/// Creates a specialization of this generic context for the `tuple` class.
pub(crate) fn specialize_tuple(
self,
db: &'db dyn Db,
element_type: Type<'db>,
tuple: TupleType<'db>,
) -> Specialization<'db> {
Specialization::new(db, self, Box::from([element_type]), Some(tuple))
debug_assert!(
matches!(self.inner(db), GenericContextInner::Tuple { .. }),
"Should never call `GenericContext::specialize_tuple` on a non-tuple context"
);
Specialization::new(db, self, SpecializationInner::Tuple(tuple))
}
/// Creates a specialization of this generic context. Panics if the length of `types` does not
@@ -319,8 +393,14 @@ impl<'db> GenericContext<'db> {
db: &'db dyn Db,
types: &[Option<Type<'db>>],
) -> Specialization<'db> {
if let GenericContextInner::Tuple { .. } = self.inner(db) {
assert_eq!(types.len(), 1);
let ty = types[0].unwrap_or_else(Type::unknown);
return self.specialize_tuple(db, TupleType::homogeneous(db, ty));
}
let variables = self.variables(db);
assert!(variables.len() == types.len());
assert_eq!(variables.len(), types.len());
// Typevars can have other typevars as their default values, e.g.
//
@@ -353,20 +433,40 @@ impl<'db> GenericContext<'db> {
expanded[idx] = default;
}
Specialization::new(db, self, expanded.into_boxed_slice(), None)
Specialization::new(
db,
self,
SpecializationInner::NonTuple(expanded.into_boxed_slice()),
)
}
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
let variables: FxOrderSet<_> = self
.variables(db)
.iter()
.map(|bound_typevar| bound_typevar.normalized_impl(db, visitor))
.collect();
Self::new(db, variables)
let inner = match self.inner(db) {
GenericContextInner::Tuple { single_typevar } => {
let single_typevar = single_typevar.normalized_impl(db, visitor);
GenericContextInner::Tuple { single_typevar }
}
GenericContextInner::NonTuple(variables) => {
let variables: FxOrderSet<_> = variables
.into_iter()
.map(|bound_typevar| bound_typevar.normalized_impl(db, visitor))
.collect();
GenericContextInner::NonTuple(variables)
}
};
Self::new(db, inner)
}
fn heap_size((variables,): &(FxOrderSet<BoundTypeVarInstance<'db>>,)) -> usize {
ruff_memory_usage::order_set_heap_size(variables)
fn heap_size((inner,): &(GenericContextInner<'db>,)) -> usize {
match inner {
GenericContextInner::Tuple { single_typevar } => {
ruff_memory_usage::heap_size(single_typevar)
}
GenericContextInner::NonTuple(variables) => {
ruff_memory_usage::order_set_heap_size(variables)
}
}
}
}
@@ -391,6 +491,12 @@ impl std::fmt::Display for LegacyGenericBase {
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, get_size2::GetSize)]
pub enum SpecializationInner<'db> {
Tuple(TupleType<'db>),
NonTuple(Box<[Type<'db>]>),
}
/// An assignment of a specific type to each type variable in a generic scope.
///
/// TODO: Handle nested specializations better, with actual parent links to the specialization of
@@ -398,12 +504,8 @@ impl std::fmt::Display for LegacyGenericBase {
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
pub struct Specialization<'db> {
pub(crate) generic_context: GenericContext<'db>,
#[returns(deref)]
pub(crate) types: Box<[Type<'db>]>,
/// For specializations of `tuple`, we also store more detailed information about the tuple's
/// elements, above what the class's (single) typevar can represent.
tuple_inner: Option<TupleType<'db>>,
#[returns(ref)]
inner: SpecializationInner<'db>,
}
// The Salsa heap is tracked separately.
@@ -418,15 +520,29 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si
for ty in specialization.types(db) {
visitor.visit_type(db, *ty);
}
if let Some(tuple) = specialization.tuple_inner(db) {
walk_tuple_type(db, tuple, visitor);
}
}
impl<'db> Specialization<'db> {
pub(crate) fn types(self, db: &'db dyn Db) -> &'db [Type<'db>] {
#[salsa::tracked(returns(ref), heap_size=ruff_memory_usage::heap_size)]
fn homogeneous_element_type<'db>(db: &'db dyn Db, tuple: TupleType<'db>) -> Type<'db> {
tuple.tuple(db).homogeneous_element_type(db)
}
match self.inner(db) {
SpecializationInner::Tuple(tuple) => {
std::slice::from_ref(homogeneous_element_type(db, *tuple))
}
SpecializationInner::NonTuple(types) => types,
}
}
/// Returns the tuple spec for a specialization of the `tuple` class.
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db))
match self.inner(db) {
SpecializationInner::Tuple(tuple) => Some(tuple.tuple(db)),
SpecializationInner::NonTuple(_) => None,
}
}
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
@@ -436,11 +552,18 @@ impl<'db> Specialization<'db> {
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
) -> Option<Type<'db>> {
let index = self
.generic_context(db)
.variables(db)
.get_index_of(&bound_typevar)?;
self.types(db).get(index).copied()
match self.generic_context(db).inner(db) {
GenericContextInner::Tuple { single_typevar } => (*single_typevar == bound_typevar)
.then(|| {
let types = self.types(db);
assert_eq!(types.len(), 1);
types[0]
}),
GenericContextInner::NonTuple(variables) => {
let index = variables.get_index_of(&bound_typevar)?;
self.types(db).get(index).copied()
}
}
}
/// Applies a specialization to this specialization. This is used, for instance, when a generic
@@ -474,15 +597,20 @@ impl<'db> Specialization<'db> {
type_mapping: &TypeMapping<'a, 'db>,
visitor: &ApplyTypeMappingVisitor<'db>,
) -> Self {
let types: Box<[_]> = self
.types(db)
.iter()
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
.collect();
let tuple_inner = self
.tuple_inner(db)
.and_then(|tuple| tuple.apply_type_mapping_impl(db, type_mapping, visitor));
Specialization::new(db, self.generic_context(db), types, tuple_inner)
let inner = match self.inner(db) {
SpecializationInner::Tuple(tuple) => tuple
.apply_type_mapping_impl(db, type_mapping, visitor)
.map(SpecializationInner::Tuple)
.unwrap_or_else(|| SpecializationInner::NonTuple(Box::from([Type::Never]))),
SpecializationInner::NonTuple(types) => {
let types = types
.iter()
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
.collect();
SpecializationInner::NonTuple(types)
}
};
Specialization::new(db, self.generic_context(db), inner)
}
/// Applies an optional specialization to this specialization.
@@ -505,7 +633,13 @@ impl<'db> Specialization<'db> {
/// Panics if the two specializations are not for the same generic context.
pub(crate) fn combine(self, db: &'db dyn Db, other: Self) -> Self {
let generic_context = self.generic_context(db);
assert!(other.generic_context(db) == generic_context);
assert_eq!(other.generic_context(db), generic_context);
debug_assert!(
matches!(self.inner(db), SpecializationInner::NonTuple(_)),
"The tuple constructor is special-cased everywhere"
);
// TODO special-casing Unknown to mean "no mapping" is not right here, and can give
// confusing/wrong results in cases where there was a mapping found for a typevar, and it
// was of type Unknown. We should probably add a bitset or similar to Specialization that
@@ -519,44 +653,60 @@ impl<'db> Specialization<'db> {
_ => UnionType::from_elements(db, [self_type, other_type]),
})
.collect();
// TODO: Combine the tuple specs too
Specialization::new(db, self.generic_context(db), types, None)
Specialization::new(
db,
self.generic_context(db),
SpecializationInner::NonTuple(types),
)
}
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
let types: Box<[_]> = self
.types(db)
.iter()
.map(|ty| ty.normalized_impl(db, visitor))
.collect();
let tuple_inner = self
.tuple_inner(db)
.and_then(|tuple| tuple.normalized_impl(db, visitor));
let inner = match self.inner(db) {
SpecializationInner::Tuple(tuple) => tuple
.normalized_impl(db, visitor)
.map(SpecializationInner::Tuple)
.unwrap_or_else(|| SpecializationInner::NonTuple(Box::from([Type::Never]))),
SpecializationInner::NonTuple(types) => {
let types: Box<[_]> = types
.iter()
.map(|ty| ty.normalized_impl(db, visitor))
.collect();
SpecializationInner::NonTuple(types)
}
};
let context = self.generic_context(db).normalized_impl(db, visitor);
Self::new(db, context, types, tuple_inner)
Self::new(db, context, inner)
}
pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self {
let types: Box<[_]> = self
.generic_context(db)
.variables(db)
.into_iter()
.zip(self.types(db))
.map(|(bound_typevar, vartype)| {
let variance = match bound_typevar.typevar(db).variance(db) {
TypeVarVariance::Invariant => TypeVarVariance::Invariant,
TypeVarVariance::Covariant => variance,
TypeVarVariance::Contravariant => variance.flip(),
TypeVarVariance::Bivariant => unreachable!(),
};
vartype.materialize(db, variance)
})
.collect();
let tuple_inner = self.tuple_inner(db).and_then(|tuple| {
// Tuples are immutable, so tuple element types are always in covariant position.
tuple.materialize(db, variance)
});
Specialization::new(db, self.generic_context(db), types, tuple_inner)
let inner = match self.inner(db) {
SpecializationInner::Tuple(tuple) => SpecializationInner::Tuple(
tuple
.materialize(db, variance)
.unwrap_or_else(|| TupleType::empty(db)),
),
SpecializationInner::NonTuple(types) => {
let types: Box<[_]> = self
.generic_context(db)
.variables(db)
.zip(types)
.map(|(bound_typevar, vartype)| {
let variance = match bound_typevar.typevar(db).variance(db) {
TypeVarVariance::Invariant => TypeVarVariance::Invariant,
TypeVarVariance::Covariant => variance,
TypeVarVariance::Contravariant => variance.flip(),
TypeVarVariance::Bivariant => unreachable!(),
};
vartype.materialize(db, variance)
})
.collect();
SpecializationInner::NonTuple(types)
}
};
Specialization::new(db, self.generic_context(db), inner)
}
pub(crate) fn has_relation_to_impl(
@@ -571,12 +721,14 @@ impl<'db> Specialization<'db> {
return false;
}
if let (Some(self_tuple), Some(other_tuple)) = (self.tuple_inner(db), other.tuple_inner(db))
if let (SpecializationInner::Tuple(left), SpecializationInner::Tuple(right)) =
(self.inner(db), other.inner(db))
{
return self_tuple.has_relation_to_impl(db, other_tuple, relation, visitor);
return left.has_relation_to_impl(db, *right, relation, visitor);
}
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
for ((bound_typevar, self_type), other_type) in generic_context
.variables(db)
.zip(self.types(db))
.zip(other.types(db))
{
@@ -623,7 +775,8 @@ impl<'db> Specialization<'db> {
return false;
}
for ((bound_typevar, self_type), other_type) in (generic_context.variables(db).into_iter())
for ((bound_typevar, self_type), other_type) in generic_context
.variables(db)
.zip(self.types(db))
.zip(other.types(db))
{
@@ -644,17 +797,14 @@ impl<'db> Specialization<'db> {
}
}
match (self.tuple_inner(db), other.tuple_inner(db)) {
(Some(_), None) | (None, Some(_)) => return false,
(None, None) => {}
(Some(self_tuple), Some(other_tuple)) => {
if !self_tuple.is_equivalent_to(db, other_tuple) {
return false;
}
match (self.inner(db), other.inner(db)) {
(SpecializationInner::Tuple(_), SpecializationInner::NonTuple(_))
| (SpecializationInner::NonTuple(_), SpecializationInner::Tuple(_)) => false,
(SpecializationInner::NonTuple(_), SpecializationInner::NonTuple(_)) => true,
(SpecializationInner::Tuple(self_tuple), SpecializationInner::Tuple(other_tuple)) => {
self_tuple.is_equivalent_to(db, *other_tuple)
}
}
true
}
pub(crate) fn find_legacy_typevars(
@@ -700,11 +850,19 @@ impl<'db> PartialSpecialization<'_, 'db> {
db: &'db dyn Db,
bound_typevar: BoundTypeVarInstance<'db>,
) -> Option<Type<'db>> {
let index = self
.generic_context
.variables(db)
.get_index_of(&bound_typevar)?;
self.types.get(index).copied()
match self.generic_context.inner(db) {
GenericContextInner::Tuple { single_typevar } => {
if bound_typevar == *single_typevar {
self.types.first().copied()
} else {
None
}
}
GenericContextInner::NonTuple(variables) => {
let index = variables.get_index_of(&bound_typevar)?;
self.types.get(index).copied()
}
}
}
pub(crate) fn to_owned(&self) -> PartialSpecialization<'db, 'db> {
@@ -749,18 +907,30 @@ impl<'db> SpecializationBuilder<'db> {
}
pub(crate) fn build(&mut self, generic_context: GenericContext<'db>) -> Specialization<'db> {
let types: Box<[_]> = generic_context
.variables(self.db)
.iter()
.map(|variable| {
self.types
.get(variable)
let inner = match generic_context.inner(self.db) {
GenericContextInner::Tuple { single_typevar } => {
let ty = self
.types
.get(single_typevar)
.copied()
.unwrap_or(variable.default_type(self.db).unwrap_or(Type::unknown()))
})
.collect();
// TODO Infer the tuple spec for a tuple type
Specialization::new(self.db, generic_context, types, None)
.unwrap_or_else(Type::unknown);
SpecializationInner::Tuple(TupleType::homogeneous(self.db, ty))
}
GenericContextInner::NonTuple(variables) => {
let types: Box<[_]> = variables
.iter()
.map(|bound_typevar| {
self.types.get(bound_typevar).copied().unwrap_or(
bound_typevar
.default_type(self.db)
.unwrap_or_else(Type::unknown),
)
})
.collect();
SpecializationInner::NonTuple(types)
}
};
Specialization::new(self.db, generic_context, inner)
}
fn add_type_mapping(&mut self, bound_typevar: BoundTypeVarInstance<'db>, ty: Type<'db>) {

View File

@@ -110,7 +110,7 @@ use crate::types::enums::is_enum_class;
use crate::types::function::{
FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral,
};
use crate::types::generics::{GenericContext, bind_typevar};
use crate::types::generics::{GenericContext, GenericContextInner, bind_typevar};
use crate::types::instance::SliceLiteral;
use crate::types::mro::MroErrorKind;
use crate::types::signatures::{CallableSignature, Signature};
@@ -9061,7 +9061,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
})
.collect();
typevars.map(|typevars| GenericContext::new(self.db(), typevars))
typevars
.map(|typevars| GenericContext::new(self.db(), GenericContextInner::NonTuple(typevars)))
}
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {

View File

@@ -131,16 +131,6 @@ pub struct TupleType<'db> {
pub(crate) tuple: TupleSpec<'db>,
}
pub(super) fn walk_tuple_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
db: &'db dyn Db,
tuple: TupleType<'db>,
visitor: &V,
) {
for element in tuple.tuple(db).all_elements() {
visitor.visit_type(db, *element);
}
}
// The Salsa heap is tracked separately.
impl get_size2::GetSize for TupleType<'_> {}
@@ -206,10 +196,9 @@ impl<'db> TupleType<'db> {
tuple_class.apply_specialization(db, |generic_context| {
if generic_context.variables(db).len() == 1 {
let element_type = self.tuple(db).homogeneous_element_type(db);
generic_context.specialize_tuple(db, element_type, self)
generic_context.specialize_tuple(db, self)
} else {
generic_context.default_specialization(db, Some(KnownClass::Tuple))
generic_context.default_specialization(db)
}
})
}
@@ -290,9 +279,9 @@ fn to_class_type_cycle_initial<'db>(db: &'db dyn Db, self_: TupleType<'db>) -> C
tuple_class.apply_specialization(db, |generic_context| {
if generic_context.variables(db).len() == 1 {
generic_context.specialize_tuple(db, Type::Never, self_)
generic_context.specialize_tuple(db, self_)
} else {
generic_context.default_specialization(db, Some(KnownClass::Tuple))
generic_context.default_specialization(db)
}
})
}