1+ use std:: borrow:: Cow ;
12use std:: hash:: BuildHasherDefault ;
23use std:: sync:: { LazyLock , Mutex } ;
34
@@ -22,9 +23,9 @@ use crate::types::signatures::{CallableSignature, Parameter, Parameters, Signatu
2223use crate :: types:: tuple:: { TupleSpec , TupleType } ;
2324use crate :: types:: {
2425 BareTypeAliasType , Binding , BoundSuperError , BoundSuperType , CallableType , DataclassParams ,
25- DeprecatedInstance , DynamicType , KnownInstanceType , TypeAliasType , TypeMapping , TypeRelation ,
26- TypeTransformer , TypeVarBoundOrConstraints , TypeVarInstance , TypeVarKind , declaration_type ,
27- infer_definition_types,
26+ DeprecatedInstance , DynamicType , KnownInstanceType , NominalInstanceType , TypeAliasType ,
27+ TypeMapping , TypeRelation , TypeTransformer , TypeVarBoundOrConstraints , TypeVarInstance ,
28+ TypeVarKind , declaration_type , infer_definition_types,
2829} ;
2930use crate :: {
3031 Db , FxIndexMap , FxOrderSet , KnownModule , Program ,
@@ -1013,6 +1014,59 @@ impl<'db> ClassType<'db> {
10131014 }
10141015 }
10151016 }
1017+
1018+ /// If this class is `tuple`, a specialization of `tuple` (`tuple[int, str]`, etc.),
1019+ /// *or a subclass of `tuple`, or a subclass of a specialization of `tuple`*,
1020+ /// return its tuple specification.
1021+ pub ( super ) fn tuple_spec ( self , db : & ' db dyn Db ) -> Option < Cow < ' db , TupleSpec < ' db > > > {
1022+ self . iter_mro ( db)
1023+ . filter_map ( ClassBase :: into_class)
1024+ . find_map ( |class| class. own_tuple_spec ( db) )
1025+ }
1026+
1027+ /// If this class is `tuple` or a specialization of `tuple` (`tuple[int, str]`, etc.),
1028+ /// return its tuple specification.
1029+ ///
1030+ /// You usually don't want to use this method directly, because you usually want to consider
1031+ /// any subclass of a certain tuple type in the same way as that tuple type itself.
1032+ /// Use [`ClassType::tuple_spec`] instead.
1033+ pub ( super ) fn own_tuple_spec ( self , db : & ' db dyn Db ) -> Option < Cow < ' db , TupleSpec < ' db > > > {
1034+ let ( class_literal, specialization) = self . class_literal ( db) ;
1035+ match class_literal. known ( db) ? {
1036+ KnownClass :: Tuple => Some (
1037+ specialization
1038+ . map ( |spec| Cow :: Borrowed ( spec. tuple ( db) ) )
1039+ . unwrap_or_else ( || Cow :: Owned ( TupleSpec :: homogeneous ( Type :: unknown ( ) ) ) ) ,
1040+ ) ,
1041+ KnownClass :: VersionInfo => {
1042+ let python_version = Program :: get ( db) . python_version ( db) ;
1043+ let int_instance_ty = KnownClass :: Int . to_instance ( db) ;
1044+
1045+ // TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
1046+ let release_level_ty = {
1047+ let elements: Box < [ Type < ' db > ] > = [ "alpha" , "beta" , "candidate" , "final" ]
1048+ . iter ( )
1049+ . map ( |level| Type :: string_literal ( db, level) )
1050+ . collect ( ) ;
1051+
1052+ // For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
1053+ // those techniques ensure that union elements are deduplicated and unions are eagerly simplified
1054+ // into other types where necessary. Here, however, we know that there are no duplicates
1055+ // in this union, so it's probably more efficient to use `UnionType::new()` directly.
1056+ Type :: Union ( UnionType :: new ( db, elements) )
1057+ } ;
1058+
1059+ Some ( Cow :: Owned ( TupleSpec :: from_elements ( [
1060+ Type :: IntLiteral ( python_version. major . into ( ) ) ,
1061+ Type :: IntLiteral ( python_version. minor . into ( ) ) ,
1062+ int_instance_ty,
1063+ release_level_ty,
1064+ int_instance_ty,
1065+ ] ) ) )
1066+ }
1067+ _ => None ,
1068+ }
1069+ }
10161070}
10171071
10181072impl < ' db > From < GenericAlias < ' db > > for ClassType < ' db > {
@@ -4313,16 +4367,14 @@ impl SlotsKind {
43134367
43144368 match slots_ty {
43154369 // __slots__ = ("a", "b")
4316- Type :: Tuple ( tuple) => {
4317- let tuple = tuple. tuple ( db) ;
4318- if tuple. is_variadic ( ) {
4319- Self :: Dynamic
4320- } else if tuple. is_empty ( ) {
4321- Self :: Empty
4322- } else {
4323- Self :: NotEmpty
4324- }
4325- }
4370+ Type :: NominalInstance ( NominalInstanceType { class, .. } ) => match class
4371+ . tuple_spec ( db)
4372+ . and_then ( |spec| spec. len ( ) . into_fixed_length ( ) )
4373+ {
4374+ Some ( 0 ) => Self :: Empty ,
4375+ Some ( _) => Self :: NotEmpty ,
4376+ None => Self :: Dynamic ,
4377+ } ,
43264378
43274379 // __slots__ = "abc" # Same as `("abc",)`
43284380 Type :: StringLiteral ( _) => Self :: NotEmpty ,
0 commit comments