[ty] Pass slice to specialize (#22421)

This commit is contained in:
Micha Reiser
2026-01-09 09:45:39 +01:00
committed by GitHub
parent c5f6a74da5
commit ba5dd5837c
12 changed files with 99 additions and 85 deletions

View File

@@ -1671,7 +1671,7 @@ mod implicit_globals {
Place::Defined(
DefinedPlace::new(KnownClass::Dict.to_specialized_instance(
db,
[Type::any(), KnownClass::Int.to_instance(db)],
&[Type::any(), KnownClass::Int.to_instance(db)],
))
.with_definedness(Definedness::PossiblyUndefined),
)
@@ -1689,7 +1689,7 @@ mod implicit_globals {
),
KnownClass::Dict.to_specialized_instance(
db,
[KnownClass::Str.to_instance(db), Type::any()],
&[KnownClass::Str.to_instance(db), Type::any()],
),
);
Place::Defined(

View File

@@ -4127,7 +4127,7 @@ impl<'db> Type<'db> {
.with_annotated_type(
KnownClass::Dict.to_specialized_instance(
db,
[str_instance, Type::any()],
&[str_instance, Type::any()],
),
),
],
@@ -4377,7 +4377,7 @@ impl<'db> Type<'db> {
.with_annotated_type(
KnownClass::Iterable.to_specialized_instance(
db,
[Type::TypeVar(element_ty)],
&[Type::TypeVar(element_ty)],
),
)],
),
@@ -5840,7 +5840,7 @@ impl<'db> Type<'db> {
pub(crate) fn dunder_class(self, db: &'db dyn Db) -> Type<'db> {
if self.is_typed_dict() {
return KnownClass::Dict
.to_specialized_class_type(db, [KnownClass::Str.to_instance(db), Type::object()])
.to_specialized_class_type(db, &[KnownClass::Str.to_instance(db), Type::object()])
.map(Type::from)
// Guard against user-customized typesheds with a broken `dict` class
.unwrap_or_else(Type::unknown);
@@ -8370,7 +8370,7 @@ impl<'db> BoundTypeVarInstance<'db> {
let upper_bound = TypeVarBoundOrConstraints::UpperBound(match kind {
ParamSpecAttrKind::Args => Type::homogeneous_tuple(db, Type::object()),
ParamSpecAttrKind::Kwargs => KnownClass::Dict
.to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()])
.to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::any()])
.top_materialization(db),
});
@@ -13079,11 +13079,11 @@ pub(crate) mod tests {
let recursive = UnionType::from_elements(
&db,
[
KnownClass::List.to_specialized_instance(&db, [div]),
KnownClass::List.to_specialized_instance(&db, &[div]),
Type::none(&db),
],
);
let nested_rec = KnownClass::List.to_specialized_instance(&db, [recursive]);
let nested_rec = KnownClass::List.to_specialized_instance(&db, &[recursive]);
assert_eq!(
nested_rec.display(&db).to_string(),
"list[list[Divergent] | None]"

View File

@@ -603,7 +603,7 @@ impl<'db> BoundSuperType<'db> {
}
return delegate_to(
KnownClass::Dict
.to_specialized_instance(db, [key_builder.build(), value_builder.build()]),
.to_specialized_instance(db, &[key_builder.build(), value_builder.build()]),
);
}
Type::NewTypeInstance(newtype) => {

View File

@@ -662,7 +662,7 @@ impl<'db> Bindings<'db> {
if let Some(enum_instance) = bound_method.self_instance(db).to_instance(db)
{
overload.set_return_type(
KnownClass::Iterator.to_specialized_instance(db, [enum_instance]),
KnownClass::Iterator.to_specialized_instance(db, &[enum_instance]),
);
}
}
@@ -993,7 +993,7 @@ impl<'db> Bindings<'db> {
let specialization = UnionType::from_elements(db, member_names);
overload.set_return_type(
KnownClass::FrozenSet
.to_specialized_instance(db, [specialization]),
.to_specialized_instance(db, &[specialization]),
);
}
}

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::fmt::Write;
use std::sync::{LazyLock, Mutex};
@@ -1091,7 +1092,7 @@ impl<'db> ClassType<'db> {
let mut tuple_elements = tuple.iter_all_elements();
iterable_parameter = iterable_parameter.with_annotated_type(
KnownClass::Iterable
.to_specialized_instance(db, [tuple_elements.next().unwrap()]),
.to_specialized_instance(db, &[tuple_elements.next().unwrap()]),
);
assert_eq!(
tuple_elements.next(),
@@ -2061,7 +2062,7 @@ impl<'db> ClassLiteral<'db> {
let name = Type::string_literal(db, self.name(db));
let bases = Type::heterogeneous_tuple(db, self.explicit_bases(db));
let namespace = KnownClass::Dict
.to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()]);
.to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::any()]);
// TODO: Other keyword arguments?
let arguments = CallArguments::positional([name, bases, namespace]);
@@ -2308,9 +2309,9 @@ impl<'db> ClassLiteral<'db> {
return Member {
inner: Place::declared(KnownClass::Dict.to_specialized_instance(
db,
[
&[
KnownClass::Str.to_instance(db),
KnownClass::Field.to_specialized_instance(db, [Type::any()]),
KnownClass::Field.to_specialized_instance(db, &[Type::any()]),
],
))
.with_qualifiers(TypeQualifiers::CLASS_VAR),
@@ -5197,18 +5198,26 @@ impl KnownClass {
///
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
/// number of types, a debug-level log message will be emitted stating this.
pub(crate) fn to_specialized_class_type<'db>(
pub(crate) fn to_specialized_class_type<'t, 'db, T>(
self,
db: &'db dyn Db,
specialization: impl IntoIterator<Item = Type<'db>>,
) -> Option<ClassType<'db>> {
fn to_specialized_class_type_impl<'db>(
specialization: T,
) -> Option<ClassType<'db>>
where
T: Into<Cow<'t, [Type<'db>]>>,
'db: 't,
{
fn inner<'db>(
db: &'db dyn Db,
class: KnownClass,
class_literal: ClassLiteral<'db>,
specialization: Box<[Type<'db>]>,
generic_context: GenericContext<'db>,
) -> ClassType<'db> {
specialization: Cow<[Type<'db>]>,
) -> Option<ClassType<'db>> {
let Type::ClassLiteral(class_literal) = class.to_class_literal(db) else {
return None;
};
let generic_context = class_literal.generic_context(db)?;
if specialization.len() != generic_context.len(db) {
// a cache of the `KnownClass`es that we have already seen mismatched-arity
// specializations for (and therefore that we've already logged a warning for)
@@ -5217,31 +5226,21 @@ impl KnownClass {
if MESSAGES.lock().unwrap().insert(class) {
tracing::info!(
"Wrong number of types when specializing {}. \
Falling back to default specialization for the symbol instead.",
Falling back to default specialization for the symbol instead.",
class.display(db)
);
}
return class_literal.default_specialization(db);
return Some(class_literal.default_specialization(db));
}
class_literal
.apply_specialization(db, |_| generic_context.specialize(db, specialization))
Some(
class_literal
.apply_specialization(db, |_| generic_context.specialize(db, specialization)),
)
}
let Type::ClassLiteral(class_literal) = self.to_class_literal(db) else {
return None;
};
let generic_context = class_literal.generic_context(db)?;
let types = specialization.into_iter().collect::<Box<[_]>>();
Some(to_specialized_class_type_impl(
db,
self,
class_literal,
types,
generic_context,
))
let specialization = specialization.into();
inner(db, self, specialization)
}
/// Lookup a [`KnownClass`] in typeshed and return a [`Type`]
@@ -5250,11 +5249,15 @@ impl KnownClass {
/// If the class cannot be found in typeshed, or if you provide a specialization with the wrong
/// number of types, a debug-level log message will be emitted stating this.
#[track_caller]
pub(crate) fn to_specialized_instance<'db>(
pub(crate) fn to_specialized_instance<'t, 'db, T>(
self,
db: &'db dyn Db,
specialization: impl IntoIterator<Item = Type<'db>>,
) -> Type<'db> {
specialization: T,
) -> Type<'db>
where
T: Into<Cow<'t, [Type<'db>]>>,
'db: 't,
{
debug_assert_ne!(
self,
KnownClass::Tuple,

View File

@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::fmt::Display;
@@ -500,13 +501,17 @@ impl<'db> GenericContext<'db> {
/// Returns a specialization of this generic context where each typevar is mapped to itself.
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
let types = self.variables(db).map(Type::TypeVar).collect();
let types: Vec<Type> = 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.len(db)];
self.specialize(db, types.into())
match self.len(db) {
0 => self.specialize(db, &[]),
1 => self.specialize(db, &[Type::unknown(); 1]),
2 => self.specialize(db, &[Type::unknown(); 2]),
len => self.specialize(db, vec![Type::unknown(); len]),
}
}
pub(crate) fn is_subset_of(self, db: &'db dyn Db, other: GenericContext<'db>) -> bool {
@@ -545,11 +550,13 @@ impl<'db> GenericContext<'db> {
/// otherwise, you will be left with a partial specialization. (Use
/// [`specialize_recursive`](Self::specialize_recursive) if your types might mention typevars
/// in this generic context.)
pub(crate) fn specialize(
self,
db: &'db dyn Db,
types: Box<[Type<'db>]>,
) -> Specialization<'db> {
pub(crate) fn specialize<'t, T>(self, db: &'db dyn Db, types: T) -> Specialization<'db>
where
T: Into<Cow<'t, [Type<'db>]>>,
'db: 't,
{
let types = types.into();
assert_eq!(self.len(db), types.len());
Specialization::new(db, self, types, None, None)
}

View File

@@ -2836,7 +2836,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
KnownClass::Dict.to_specialized_instance(
self.db(),
[KnownClass::Str.to_instance(self.db()), Type::unknown()],
&[KnownClass::Str.to_instance(self.db()), Type::unknown()],
)
}
@@ -2848,14 +2848,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// The diagnostic for this case is handled in `in_type_expression`.
KnownClass::Dict.to_specialized_instance(
self.db(),
[KnownClass::Str.to_instance(self.db()), Type::unknown()],
&[KnownClass::Str.to_instance(self.db()), Type::unknown()],
)
}
}
} else {
KnownClass::Dict.to_specialized_instance(
self.db(),
[KnownClass::Str.to_instance(self.db()), annotated_type],
&[KnownClass::Str.to_instance(self.db()), annotated_type],
)
};
self.add_declaration_with_binding(
@@ -2866,7 +2866,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} else {
let inferred_ty = KnownClass::Dict.to_specialized_instance(
self.db(),
[KnownClass::Str.to_instance(self.db()), Type::unknown()],
&[KnownClass::Str.to_instance(self.db()), Type::unknown()],
);
self.add_binding(parameter.into(), definition)
@@ -3392,7 +3392,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} else {
KnownClass::BaseExceptionGroup
};
class.to_specialized_instance(self.db(), [symbol_ty])
class.to_specialized_instance(self.db(), &[symbol_ty])
} else {
symbol_ty
}
@@ -8007,7 +8007,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let infer_elt_ty = |builder: &mut Self, elt, tcx| builder.infer_expression(elt, tcx);
self.infer_collection_literal(elts, tcx, infer_elt_ty, KnownClass::List)
.unwrap_or_else(|| {
KnownClass::List.to_specialized_instance(self.db(), [Type::unknown()])
KnownClass::List.to_specialized_instance(self.db(), &[Type::unknown()])
})
}
@@ -8022,7 +8022,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
let infer_elt_ty = |builder: &mut Self, elt, tcx| builder.infer_expression(elt, tcx);
self.infer_collection_literal(elts, tcx, infer_elt_ty, KnownClass::Set)
.unwrap_or_else(|| {
KnownClass::Set.to_specialized_instance(self.db(), [Type::unknown()])
KnownClass::Set.to_specialized_instance(self.db(), &[Type::unknown()])
})
}
@@ -8049,7 +8049,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// unsupported.
if let Some(Type::Dynamic(DynamicType::Todo(_))) = tcx.annotation {
return KnownClass::Dict
.to_specialized_instance(self.db(), [Type::unknown(), Type::unknown()]);
.to_specialized_instance(self.db(), &[Type::unknown(), Type::unknown()]);
}
let items = items
@@ -8069,7 +8069,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.infer_collection_literal(items, tcx, infer_elt_ty, KnownClass::Dict)
.unwrap_or_else(|| {
KnownClass::Dict
.to_specialized_instance(self.db(), [Type::unknown(), Type::unknown()])
.to_specialized_instance(self.db(), &[Type::unknown(), Type::unknown()])
})
}
@@ -8339,11 +8339,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if evaluation_mode.is_async() {
KnownClass::AsyncGeneratorType
.to_specialized_instance(self.db(), [yield_type, Type::none(self.db())])
.to_specialized_instance(self.db(), &[yield_type, Type::none(self.db())])
} else {
KnownClass::GeneratorType.to_specialized_instance(
self.db(),
[yield_type, Type::none(self.db()), Type::none(self.db())],
&[yield_type, Type::none(self.db()), Type::none(self.db())],
)
}
}
@@ -8373,20 +8373,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.zip(inferred_element_types.iter())
.all(|(annotated, inferred)| inferred.is_assignable_to(self.db(), *annotated))
{
collection_class
.to_specialized_instance(self.db(), annotated_element_types.iter().copied())
collection_class.to_specialized_instance(self.db(), annotated_element_types)
} else {
collection_class.to_specialized_instance(
self.db(),
inferred_element_types.iter().map(|ty| {
UnionType::from_elements(
self.db(),
[
ty.promote_literals(self.db(), TypeContext::default()),
Type::unknown(),
],
)
}),
inferred_element_types
.iter()
.map(|ty| {
UnionType::from_elements(
self.db(),
[
ty.promote_literals(self.db(), TypeContext::default()),
Type::unknown(),
],
)
})
.collect::<Vec<_>>(),
)
}
}
@@ -12195,7 +12197,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.expect("A known stdlib class is available");
return class
.to_specialized_class_type(self.db(), [element_ty])
.to_specialized_class_type(self.db(), &[element_ty])
.map(Type::from)
.unwrap_or_else(Type::unknown);
}
@@ -12253,7 +12255,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
.expect("Stdlib class available");
return class
.to_specialized_class_type(self.db(), [first_ty, second_ty])
.to_specialized_class_type(self.db(), &[first_ty, second_ty])
.map(Type::from)
.unwrap_or_else(Type::unknown);
}
@@ -13115,7 +13117,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
type_to_slice_argument(ty_step),
) {
(SliceArg::Arg(lower), SliceArg::Arg(upper), SliceArg::Arg(step)) => {
KnownClass::Slice.to_specialized_instance(self.db(), [lower, upper, step])
KnownClass::Slice.to_specialized_instance(self.db(), &[lower, upper, step])
}
_ => KnownClass::Slice.to_instance(self.db()),
}

View File

@@ -793,7 +793,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
todo_type!("specialized recursive generic type alias"),
generic_context.len(db),
)
.collect(),
.collect::<Vec<_>>(),
),
);
return if in_type_expression {
@@ -1131,7 +1131,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
let ty = class.to_specialized_instance(
self.db(),
args.iter().map(|node| self.infer_type_expression(node)),
args.iter()
.map(|node| self.infer_type_expression(node))
.collect::<Vec<_>>(),
);
if arguments.is_tuple_expr() {
self.store_expression_type(arguments, ty);

View File

@@ -235,7 +235,7 @@ mod stable {
// `Iterable` but assigns `__iter__ = None` in the class body (or similar).
type_property_test!(
all_types_assignable_to_iterable_are_iterable, db,
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, [Type::object()])) => t.try_iterate(db).is_ok()
forall types t. t.is_assignable_to(db, KnownClass::Iterable.to_specialized_instance(db, &[Type::object()])) => t.try_iterate(db).is_ok()
);
// Our optimized `Type::negate()` function should always produce the exact same type

View File

@@ -1015,7 +1015,7 @@ impl<'db> Type<'db> {
// key types are a supertype of the extra items type?)
(Type::TypedDict(_), _) => relation_visitor.visit((self, target, relation), || {
KnownClass::Mapping
.to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::object()])
.to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::object()])
.has_relation_to_impl(
db,
target,
@@ -2384,7 +2384,7 @@ impl<'db> Type<'db> {
// `dict` *itself* is almost always disjoint from `TypedDict` -- but it's a good
// approximation, and some false negatives are acceptable.
(Type::TypedDict(_), other) | (other, Type::TypedDict(_)) => KnownClass::Dict
.to_specialized_instance(db, [KnownClass::Str.to_instance(db), Type::any()])
.to_specialized_instance(db, &[KnownClass::Str.to_instance(db), Type::any()])
.has_relation_to_impl(
db,
other,

View File

@@ -699,7 +699,7 @@ impl<'db> Signature<'db> {
pub(super) fn wrap_coroutine_return_type(self, db: &'db dyn Db) -> Self {
let return_ty = KnownClass::CoroutineType
.to_specialized_instance(db, [Type::any(), Type::any(), self.return_ty]);
.to_specialized_instance(db, &[Type::any(), Type::any(), self.return_ty]);
Self { return_ty, ..self }
}

View File

@@ -1898,7 +1898,7 @@ impl<'db> TupleUnpacker<'db> {
.into_all_elements_with_kind()
.map(|builder| match builder {
TupleElement::Variable(builder) => builder.try_build().unwrap_or_else(|| {
KnownClass::List.to_specialized_instance(self.db, [Type::unknown()])
KnownClass::List.to_specialized_instance(self.db, &[Type::unknown()])
}),
TupleElement::Fixed(builder)
| TupleElement::Prefix(builder)
@@ -1927,7 +1927,7 @@ impl<'db> VariableLengthTuple<UnionBuilder<'db>> {
target.add_in_place(value);
}
self.variable_element_mut()
.add_in_place(KnownClass::List.to_specialized_instance(db, [values.variable()]));
.add_in_place(KnownClass::List.to_specialized_instance(db, &[values.variable()]));
for (target, value) in
(self.suffix_elements_mut().iter_mut()).zip(values.iter_suffix_elements())
{