[ty] Return Option<TupleType> from infer_tuple_type_expression (#19735)

## Summary

This PR reduces the virality of some of the `Todo` types in
`infer_tuple_type_expression`. Rather than inferring `Todo`, we instead
infer `tuple[Todo, ...]`. This reflects the fact that whatever the
contents of the slice in a `tuple[]` type expression, we would always
infer some kind of tuple type as the result of the type expression. Any
tuple type should be assignable to `tuple[Todo, ...]`, so this shouldn't
introduce any new false positives; this can be seen in the ecosystem
report.

As a result of the change, we are now able to enforce in the signature
of `Type::infer_tuple_type_expression` that it returns an
`Option<TupleType<'db>>`, which is more strongly typed and expresses
clearly the invariant that a tuple type expression should always be
inferred as a `tuple` type. To enable this, it was necessary to refactor
several `TupleType` constructors in `tuple.rs` so that they return
`Option<TupleType>` rather than `Type`; this means that callers of these
constructor functions are now free to either propagate the
`Option<TupleType<'db>>` or convert it to a `Type<'db>`.

## Test Plan

Mdtests updated.
This commit is contained in:
Alex Waygood 2025-08-04 13:48:19 +01:00 committed by GitHub
parent e4d6b54a16
commit bc6e8b58ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 156 additions and 142 deletions

View file

@ -18,5 +18,5 @@ def append_int(*args: *Ts) -> tuple[*Ts, int]:
return (*args, 1)
# TODO should be tuple[Literal[True], Literal["a"], int]
reveal_type(append_int(True, "a")) # revealed: @Todo(PEP 646)
reveal_type(append_int(True, "a")) # revealed: tuple[@Todo(PEP 646), ...]
```

View file

@ -17,6 +17,7 @@ Alias: TypeAlias = int
def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]:
reveal_type(args) # revealed: tuple[@Todo(`Unpack[]` special form), ...]
reveal_type(Alias) # revealed: @Todo(Support for `typing.TypeAlias`)
return args
def g() -> TypeGuard[int]: ...
def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P.kwargs) -> R_co:

View file

@ -59,7 +59,7 @@ reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]]
reveal_type(e) # revealed: tuple[str, ...]
reveal_type(f) # revealed: tuple[str, *tuple[int, ...], bytes]
reveal_type(g) # revealed: @Todo(PEP 646)
reveal_type(g) # revealed: tuple[@Todo(PEP 646), ...]
reveal_type(h) # revealed: tuple[list[int], list[int]]
reveal_type(i) # revealed: tuple[str | int, str | int]

View file

@ -3801,7 +3801,7 @@ impl<'db> Type<'db> {
db,
[
KnownClass::Str.to_instance(db),
TupleType::homogeneous(db, KnownClass::Str.to_instance(db)),
Type::homogeneous_tuple(db, KnownClass::Str.to_instance(db)),
],
)),
Parameter::positional_only(Some(Name::new_static("start")))
@ -4114,7 +4114,7 @@ impl<'db> Type<'db> {
Parameter::positional_only(Some(Name::new_static("name")))
.with_annotated_type(str_instance),
Parameter::positional_only(Some(Name::new_static("bases")))
.with_annotated_type(TupleType::homogeneous(
.with_annotated_type(Type::homogeneous_tuple(
db,
type_instance,
)),
@ -4304,7 +4304,7 @@ impl<'db> Type<'db> {
.with_annotated_type(Type::any())
.type_form(),
Parameter::keyword_only(Name::new_static("type_params"))
.with_annotated_type(TupleType::homogeneous(
.with_annotated_type(Type::homogeneous_tuple(
db,
UnionType::from_elements(
db,
@ -4315,7 +4315,7 @@ impl<'db> Type<'db> {
],
),
))
.with_default_type(TupleType::empty(db)),
.with_default_type(Type::empty_tuple(db)),
]),
None,
),
@ -4401,7 +4401,7 @@ impl<'db> Type<'db> {
CallableBinding::from_overloads(
self,
[
Signature::new(Parameters::empty(), Some(TupleType::empty(db))),
Signature::new(Parameters::empty(), Some(Type::empty_tuple(db))),
Signature::new(
Parameters::new([Parameter::positional_only(Some(
Name::new_static("iterable"),
@ -4409,7 +4409,7 @@ impl<'db> Type<'db> {
.with_annotated_type(
KnownClass::Iterable.to_specialized_instance(db, [object]),
)]),
Some(TupleType::homogeneous(db, object)),
Some(Type::homogeneous_tuple(db, object)),
),
],
)
@ -5267,7 +5267,7 @@ impl<'db> Type<'db> {
// We treat `typing.Type` exactly the same as `builtins.type`:
SpecialFormType::Type => Ok(KnownClass::Type.to_instance(db)),
SpecialFormType::Tuple => Ok(TupleType::homogeneous(db, Type::unknown())),
SpecialFormType::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())),
// Legacy `typing` aliases
SpecialFormType::List => Ok(KnownClass::List.to_instance(db)),
@ -5458,7 +5458,7 @@ impl<'db> Type<'db> {
Type::Union(UnionType::new(db, elements))
};
TupleType::from_elements(
Type::heterogeneous_tuple(
db,
[
Type::IntLiteral(python_version.major.into()),

View file

@ -6,7 +6,7 @@ use ruff_python_ast as ast;
use crate::Db;
use crate::types::KnownClass;
use crate::types::enums::enum_member_literals;
use crate::types::tuple::{TupleLength, TupleSpec, TupleType};
use crate::types::tuple::{TupleLength, TupleSpec};
use super::Type;
@ -246,7 +246,7 @@ fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> {
}
})
.multi_cartesian_product()
.map(|types| TupleType::from_elements(db, types))
.map(|types| Type::heterogeneous_tuple(db, types))
.collect::<Vec<_>>();
if expanded.len() == 1 {
// There are no elements in the tuple type that can be expanded.
@ -306,17 +306,17 @@ mod tests {
let false_ty = Type::BooleanLiteral(false);
// Empty tuple
let empty_tuple = TupleType::empty(&db);
let empty_tuple = Type::empty_tuple(&db);
let expanded = expand_type(&db, empty_tuple);
assert!(expanded.is_none());
// None of the elements can be expanded.
let tuple_type1 = TupleType::from_elements(&db, [int_ty, str_ty]);
let tuple_type1 = Type::heterogeneous_tuple(&db, [int_ty, str_ty]);
let expanded = expand_type(&db, tuple_type1);
assert!(expanded.is_none());
// All elements can be expanded.
let tuple_type2 = TupleType::from_elements(
let tuple_type2 = Type::heterogeneous_tuple(
&db,
[
bool_ty,
@ -324,18 +324,18 @@ mod tests {
],
);
let expected_types = [
TupleType::from_elements(&db, [true_ty, int_ty]),
TupleType::from_elements(&db, [true_ty, str_ty]),
TupleType::from_elements(&db, [true_ty, bytes_ty]),
TupleType::from_elements(&db, [false_ty, int_ty]),
TupleType::from_elements(&db, [false_ty, str_ty]),
TupleType::from_elements(&db, [false_ty, bytes_ty]),
Type::heterogeneous_tuple(&db, [true_ty, int_ty]),
Type::heterogeneous_tuple(&db, [true_ty, str_ty]),
Type::heterogeneous_tuple(&db, [true_ty, bytes_ty]),
Type::heterogeneous_tuple(&db, [false_ty, int_ty]),
Type::heterogeneous_tuple(&db, [false_ty, str_ty]),
Type::heterogeneous_tuple(&db, [false_ty, bytes_ty]),
];
let expanded = expand_type(&db, tuple_type2).unwrap();
assert_eq!(expanded, expected_types);
// Mixed set of elements where some can be expanded while others cannot be.
let tuple_type3 = TupleType::from_elements(
let tuple_type3 = Type::heterogeneous_tuple(
&db,
[
bool_ty,
@ -345,21 +345,21 @@ mod tests {
],
);
let expected_types = [
TupleType::from_elements(&db, [true_ty, int_ty, str_ty, str_ty]),
TupleType::from_elements(&db, [true_ty, int_ty, bytes_ty, str_ty]),
TupleType::from_elements(&db, [false_ty, int_ty, str_ty, str_ty]),
TupleType::from_elements(&db, [false_ty, int_ty, bytes_ty, str_ty]),
Type::heterogeneous_tuple(&db, [true_ty, int_ty, str_ty, str_ty]),
Type::heterogeneous_tuple(&db, [true_ty, int_ty, bytes_ty, str_ty]),
Type::heterogeneous_tuple(&db, [false_ty, int_ty, str_ty, str_ty]),
Type::heterogeneous_tuple(&db, [false_ty, int_ty, bytes_ty, str_ty]),
];
let expanded = expand_type(&db, tuple_type3).unwrap();
assert_eq!(expanded, expected_types);
// Variable-length tuples are not expanded.
let variable_length_tuple = TupleType::mixed(
let variable_length_tuple = Type::tuple(TupleType::mixed(
&db,
[bool_ty],
int_ty,
[UnionType::from_elements(&db, [str_ty, bytes_ty]), str_ty],
);
));
let expanded = expand_type(&db, variable_length_tuple);
assert!(expanded.is_none());
}

View file

@ -390,9 +390,9 @@ impl<'db> Bindings<'db> {
);
}
Some("__constraints__") => {
overload.set_return_type(TupleType::from_elements(
overload.set_return_type(Type::heterogeneous_tuple(
db,
typevar.constraints(db).into_iter().flatten().copied(),
typevar.constraints(db).into_iter().flatten(),
));
}
Some("__default__") => {
@ -674,7 +674,7 @@ impl<'db> Bindings<'db> {
Some(names) => {
let mut names = names.iter().collect::<Vec<_>>();
names.sort();
TupleType::from_elements(
Type::heterogeneous_tuple(
db,
names.iter().map(|name| {
Type::string_literal(db, name.as_str())
@ -694,7 +694,7 @@ impl<'db> Bindings<'db> {
let return_ty = match ty {
Type::ClassLiteral(class) => {
if let Some(metadata) = enums::enum_metadata(db, *class) {
TupleType::from_elements(
Type::heterogeneous_tuple(
db,
metadata
.members
@ -714,7 +714,7 @@ impl<'db> Bindings<'db> {
Some(KnownFunction::AllMembers) => {
if let [Some(ty)] = overload.parameter_types() {
overload.set_return_type(TupleType::from_elements(
overload.set_return_type(Type::heterogeneous_tuple(
db,
ide_support::all_members(db, *ty)
.into_iter()
@ -1458,7 +1458,7 @@ impl<'db> CallableBinding<'db> {
}
let top_materialized_argument_type =
TupleType::from_elements(db, top_materialized_argument_types);
Type::heterogeneous_tuple(db, top_materialized_argument_types);
// A flag to indicate whether we've found the overload that makes the remaining overloads
// unmatched for the given argument types.
@ -1494,7 +1494,7 @@ impl<'db> CallableBinding<'db> {
parameter_types.push(UnionType::from_elements(db, current_parameter_types));
}
if top_materialized_argument_type
.is_assignable_to(db, TupleType::from_elements(db, parameter_types))
.is_assignable_to(db, Type::heterogeneous_tuple(db, parameter_types))
{
filter_remaining_overloads = true;
}

View file

@ -20,7 +20,7 @@ use crate::types::function::{DataclassTransformerParams, KnownFunction};
use crate::types::generics::{GenericContext, Specialization, walk_specialization};
use crate::types::infer::nearest_enclosing_class;
use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType};
use crate::types::tuple::TupleSpec;
use crate::types::{
BareTypeAliasType, Binding, BoundSuperError, BoundSuperType, CallableType, DataclassParams,
DeprecatedInstance, DynamicType, KnownInstanceType, TypeAliasType, TypeMapping, TypeRelation,
@ -778,7 +778,7 @@ impl<'db> ClassType<'db> {
overload_signatures.push(synthesize_getitem_overload_signature(
KnownClass::Slice.to_instance(db),
TupleType::homogeneous(db, all_elements_unioned),
Type::homogeneous_tuple(db, all_elements_unioned),
));
let getitem_signature =
@ -841,7 +841,8 @@ impl<'db> ClassType<'db> {
// - an unspecialized tuple
// - a tuple with no minimum length
if specialization.is_none_or(|spec| spec.tuple(db).len().minimum() == 0) {
iterable_parameter = iterable_parameter.with_default_type(TupleType::empty(db));
iterable_parameter =
iterable_parameter.with_default_type(Type::empty_tuple(db));
}
let parameters = Parameters::new([
@ -1533,7 +1534,7 @@ impl<'db> ClassLiteral<'db> {
}
} else {
let name = Type::string_literal(db, self.name(db));
let bases = TupleType::from_elements(db, self.explicit_bases(db).iter().copied());
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()]);
@ -1624,8 +1625,8 @@ impl<'db> ClassLiteral<'db> {
policy: MemberLookupPolicy,
) -> PlaceAndQualifiers<'db> {
if name == "__mro__" {
let tuple_elements = self.iter_mro(db, specialization).map(Type::from);
return Place::bound(TupleType::from_elements(db, tuple_elements)).into();
let tuple_elements = self.iter_mro(db, specialization);
return Place::bound(Type::heterogeneous_tuple(db, tuple_elements)).into();
}
self.class_member_from_mro(db, name, policy, self.iter_mro(db, specialization))

View file

@ -15,7 +15,6 @@ use crate::types::string_annotation::{
IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, INVALID_SYNTAX_IN_FORWARD_ANNOTATION,
RAW_STRING_TYPE_ANNOTATION,
};
use crate::types::tuple::TupleType;
use crate::types::{SpecialFormType, Type, protocol_class::ProtocolClassLiteral};
use crate::util::diagnostics::format_enumeration;
use crate::{Db, FxIndexMap, Module, ModuleName, Program, declare_lint};
@ -2430,7 +2429,7 @@ pub(crate) fn report_invalid_or_unsupported_base(
return;
}
let tuple_of_types = TupleType::homogeneous(db, instance_of_type);
let tuple_of_types = Type::homogeneous_tuple(db, instance_of_type);
let explain_mro_entries = |diagnostic: &mut LintDiagnosticGuard| {
diagnostic.info(

View file

@ -242,7 +242,7 @@ impl<'db> GenericContext<'db> {
/// Returns a tuple type of the typevars introduced by this generic context.
pub(crate) fn as_tuple(self, db: &'db dyn Db) -> Type<'db> {
TupleType::from_elements(
Type::heterogeneous_tuple(
db,
self.variables(db)
.iter()

View file

@ -2868,7 +2868,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
todo_type!("PEP 646")
} else {
let annotated_type = self.file_expression_type(annotation);
TupleType::homogeneous(self.db(), annotated_type)
Type::homogeneous_tuple(self.db(), annotated_type)
};
self.add_declaration_with_binding(
@ -2880,7 +2880,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.add_binding(
parameter.into(),
definition,
TupleType::homogeneous(self.db(), Type::unknown()),
Type::homogeneous_tuple(self.db(), Type::unknown()),
);
}
}
@ -3293,7 +3293,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
)
} else if node_ty.is_assignable_to(
self.db(),
TupleType::homogeneous(self.db(), type_base_exception),
Type::homogeneous_tuple(self.db(), type_base_exception),
) {
extract_tuple_specialization(self.db(), node_ty)
.unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db()))
@ -3303,7 +3303,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
self.db(),
[
type_base_exception,
TupleType::homogeneous(self.db(), type_base_exception),
Type::homogeneous_tuple(self.db(), type_base_exception),
],
),
) {
@ -5611,7 +5611,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// consuming the whole iterator).
let element_types: Vec<_> = elts.iter().map(|elt| self.infer_expression(elt)).collect();
TupleType::from_elements(self.db(), element_types)
Type::heterogeneous_tuple(self.db(), element_types)
}
fn infer_list_expression(&mut self, list: &ast::ExprList) -> Type<'db> {
@ -8486,8 +8486,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// special cases, too.
if let Type::ClassLiteral(class) = value_ty {
if class.is_tuple(self.db()) {
return self
.infer_tuple_type_expression(slice)
return Type::tuple(self.infer_tuple_type_expression(slice))
.to_meta_type(self.db());
}
if let Some(generic_context) = class.generic_context(self.db()) {
@ -8500,9 +8499,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
return self
.infer_tuple_type_expression(slice)
.to_meta_type(self.db());
return Type::tuple(self.infer_tuple_type_expression(slice)).to_meta_type(self.db());
}
let slice_ty = self.infer_expression(slice);
@ -8525,7 +8522,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
);
self.store_expression_type(
slice_node,
TupleType::from_elements(self.db(), arguments.iter_types()),
Type::heterogeneous_tuple(self.db(), arguments.iter_types()),
);
arguments
}
@ -8617,7 +8614,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
};
if let Ok(new_elements) = tuple.py_slice(db, start, stop, step) {
TupleType::from_elements(db, new_elements)
Type::heterogeneous_tuple(db, new_elements)
} else {
report_slice_step_size_zero(context, value_node.into());
Type::unknown()
@ -9644,7 +9641,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let hinted_type = if list.len() == 1 {
KnownClass::List.to_specialized_instance(db, inner_types)
} else {
TupleType::from_elements(db, inner_types)
Type::heterogeneous_tuple(db, inner_types)
};
diagnostic.set_primary_message(format_args!(
@ -9676,7 +9673,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
Type::Dynamic(DynamicType::Todo(_) | DynamicType::Unknown)
)
}) {
let hinted_type = TupleType::from_elements(self.db(), inner_types);
let hinted_type = Type::heterogeneous_tuple(self.db(), inner_types);
diagnostic.set_primary_message(format_args!(
"Did you mean `{}`?",
hinted_type.display(self.db()),
@ -9895,7 +9892,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
) -> Type<'db> {
match value_ty {
Type::ClassLiteral(class_literal) => match class_literal.known(self.db()) {
Some(KnownClass::Tuple) => self.infer_tuple_type_expression(slice),
Some(KnownClass::Tuple) => Type::tuple(self.infer_tuple_type_expression(slice)),
Some(KnownClass::Type) => self.infer_subclass_of_type_expression(slice),
_ => self.infer_subscript_type_expression(subscript, value_ty),
},
@ -9920,7 +9917,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
/// Given the slice of a `tuple[]` annotation, return the type that the annotation represents
fn infer_tuple_type_expression(&mut self, tuple_slice: &ast::Expr) -> Type<'db> {
fn infer_tuple_type_expression(&mut self, tuple_slice: &ast::Expr) -> Option<TupleType<'db>> {
/// In most cases, if a subelement of the tuple is inferred as `Todo`,
/// we should only infer `Todo` for that specific subelement.
/// Certain specific AST nodes can however change the meaning of the entire tuple,
@ -9961,7 +9958,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
self.infer_expression(ellipsis);
let result =
TupleType::homogeneous(self.db(), self.infer_type_expression(element));
self.store_expression_type(tuple_slice, result);
self.store_expression_type(tuple_slice, Type::tuple(result));
return result;
}
@ -9988,15 +9985,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
}
let ty = if return_todo {
todo_type!("PEP 646")
TupleType::homogeneous(self.db(), todo_type!("PEP 646"))
} else {
Type::tuple(TupleType::new(self.db(), element_types))
TupleType::new(self.db(), element_types)
};
// Here, we store the type for the inner `int, str` tuple-expression,
// while the type for the outer `tuple[int, str]` slice-expression is
// stored in the surrounding `infer_type_expression` call:
self.store_expression_type(tuple_slice, ty);
self.store_expression_type(tuple_slice, Type::tuple(ty));
ty
}
@ -10004,7 +10001,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let single_element_ty = self.infer_type_expression(single_element);
if element_could_alter_type_of_whole_tuple(single_element, single_element_ty, self)
{
todo_type!("PEP 646")
TupleType::homogeneous(self.db(), todo_type!("PEP 646"))
} else {
TupleType::from_elements(self.db(), std::iter::once(single_element_ty))
}
@ -10668,7 +10665,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
Type::unknown()
}
SpecialFormType::Type => self.infer_subclass_of_type_expression(arguments_slice),
SpecialFormType::Tuple => self.infer_tuple_type_expression(arguments_slice),
SpecialFormType::Tuple => {
Type::tuple(self.infer_tuple_type_expression(arguments_slice))
}
SpecialFormType::Generic | SpecialFormType::Protocol => {
self.infer_expression(arguments_slice);
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {

View file

@ -19,7 +19,7 @@ impl<'db> Type<'db> {
match (class, class.known(db)) {
(_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any),
(ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => {
TupleType::homogeneous(db, Type::unknown())
Type::tuple(TupleType::homogeneous(db, Type::unknown()))
}
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => {
Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db)))

View file

@ -195,13 +195,13 @@ impl Ty {
}
Ty::FixedLengthTuple(tys) => {
let elements = tys.into_iter().map(|ty| ty.into_type(db));
TupleType::from_elements(db, elements)
Type::heterogeneous_tuple(db, elements)
}
Ty::VariableLengthTuple(prefix, variable, suffix) => {
let prefix = prefix.into_iter().map(|ty| ty.into_type(db));
let variable = variable.into_type(db);
let suffix = suffix.into_iter().map(|ty| ty.into_type(db));
TupleType::mixed(db, prefix, variable, suffix)
Type::tuple(TupleType::mixed(db, prefix, variable, suffix))
}
Ty::SubclassOfAny => SubclassOfType::subclass_of_any(),
Ty::SubclassOfBuiltinClass(s) => SubclassOfType::from(

View file

@ -151,6 +151,25 @@ impl<'db> Type<'db> {
};
Self::Tuple(tuple)
}
pub(crate) fn homogeneous_tuple(db: &'db dyn Db, element: Type<'db>) -> Self {
Type::tuple(TupleType::homogeneous(db, element))
}
pub(crate) fn heterogeneous_tuple<I, T>(db: &'db dyn Db, elements: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<Type<'db>>,
{
Type::tuple(TupleType::from_elements(
db,
elements.into_iter().map(Into::into),
))
}
pub(crate) fn empty_tuple(db: &'db dyn Db) -> Self {
Type::Tuple(TupleType::empty(db))
}
}
impl<'db> TupleType<'db> {
@ -180,18 +199,16 @@ impl<'db> TupleType<'db> {
Some(TupleType::new_internal(db, tuple_key))
}
pub(crate) fn empty(db: &'db dyn Db) -> Type<'db> {
Type::tuple(TupleType::new(
db,
TupleSpec::from(FixedLengthTuple::empty()),
))
pub(crate) fn empty(db: &'db dyn Db) -> Self {
TupleType::new(db, TupleSpec::from(FixedLengthTuple::empty()))
.expect("TupleType::new() should always return `Some` for an empty `TupleSpec`")
}
pub(crate) fn from_elements(
db: &'db dyn Db,
types: impl IntoIterator<Item = Type<'db>>,
) -> Type<'db> {
Type::tuple(TupleType::new(db, TupleSpec::from_elements(types)))
) -> Option<Self> {
TupleType::new(db, TupleSpec::from_elements(types))
}
#[cfg(test)]
@ -200,15 +217,12 @@ impl<'db> TupleType<'db> {
prefix: impl IntoIterator<Item = Type<'db>>,
variable: Type<'db>,
suffix: impl IntoIterator<Item = Type<'db>>,
) -> Type<'db> {
Type::tuple(TupleType::new(
db,
VariableLengthTuple::mixed(prefix, variable, suffix),
))
) -> Option<Self> {
TupleType::new(db, VariableLengthTuple::mixed(prefix, variable, suffix))
}
pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Type<'db> {
Type::tuple(TupleType::new(db, TupleSpec::homogeneous(element)))
pub(crate) fn homogeneous(db: &'db dyn Db, element: Type<'db>) -> Option<Self> {
TupleType::new(db, TupleSpec::homogeneous(element))
}
pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> {