mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-30 03:27:07 +00:00 
			
		
		
		
	[ty] Remove Type::Tuple (#19669)
				
					
				
			This commit is contained in:
		
							parent
							
								
									2abd683376
								
							
						
					
					
						commit
						d2fbf2af8f
					
				
					 27 changed files with 1189 additions and 1225 deletions
				
			
		|  | @ -222,7 +222,6 @@ impl<'db> Completion<'db> { | |||
|                 // "struct" here as a more general "object." ---AG
 | ||||
|                 Type::NominalInstance(_) | ||||
|                 | Type::PropertyInstance(_) | ||||
|                 | Type::Tuple(_) | ||||
|                 | Type::BoundSuper(_) | ||||
|                 | Type::TypedDict(_) => CompletionKind::Struct, | ||||
|                 Type::IntLiteral(_) | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ use crate::types::infer::infer_unpack_types; | |||
| use crate::types::mro::{Mro, MroError, MroIterator}; | ||||
| pub(crate) use crate::types::narrow::infer_narrowing_constraint; | ||||
| use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature}; | ||||
| use crate::types::tuple::{TupleSpec, TupleType}; | ||||
| use crate::types::tuple::TupleSpec; | ||||
| use crate::unpack::EvaluationMode; | ||||
| pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic; | ||||
| use crate::{Db, FxOrderMap, FxOrderSet, Module, Program}; | ||||
|  | @ -594,11 +594,6 @@ pub enum Type<'db> { | |||
|     LiteralString, | ||||
|     /// A bytes literal
 | ||||
|     BytesLiteral(BytesLiteralType<'db>), | ||||
|     /// An instance of the builtin `tuple` class.
 | ||||
|     /// TODO: Consider removing this in favor of `NominalInstance`. This is currently stored as a
 | ||||
|     /// separate variant partly for historical reasons, and partly to allow us to easily
 | ||||
|     /// distinguish tuples since they occur so often.
 | ||||
|     Tuple(TupleType<'db>), | ||||
|     /// An instance of a typevar in a generic class or function. When the generic class or function
 | ||||
|     /// is specialized, we will replace this typevar with its specialization.
 | ||||
|     TypeVar(BoundTypeVarInstance<'db>), | ||||
|  | @ -641,22 +636,25 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|     fn is_none(&self, db: &'db dyn Db) -> bool { | ||||
|         self.into_nominal_instance() | ||||
|             .is_some_and(|instance| instance.class.is_known(db, KnownClass::NoneType)) | ||||
|             .is_some_and(|instance| instance.class(db).is_known(db, KnownClass::NoneType)) | ||||
|     } | ||||
| 
 | ||||
|     fn is_bool(&self, db: &'db dyn Db) -> bool { | ||||
|         self.into_nominal_instance() | ||||
|             .is_some_and(|instance| instance.class.is_known(db, KnownClass::Bool)) | ||||
|             .is_some_and(|instance| instance.class(db).is_known(db, KnownClass::Bool)) | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_notimplemented(&self, db: &'db dyn Db) -> bool { | ||||
|         self.into_nominal_instance() | ||||
|             .is_some_and(|instance| instance.class.is_known(db, KnownClass::NotImplementedType)) | ||||
|         self.into_nominal_instance().is_some_and(|instance| { | ||||
|             instance | ||||
|                 .class(db) | ||||
|                 .is_known(db, KnownClass::NotImplementedType) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub fn is_object(&self, db: &'db dyn Db) -> bool { | ||||
|         self.into_nominal_instance() | ||||
|             .is_some_and(|instance| instance.class.is_object(db)) | ||||
|             .is_some_and(|instance| instance.is_object(db)) | ||||
|     } | ||||
| 
 | ||||
|     pub const fn is_todo(&self) -> bool { | ||||
|  | @ -685,6 +683,30 @@ impl<'db> Type<'db> { | |||
|         self.materialize(db, TypeVarVariance::Contravariant) | ||||
|     } | ||||
| 
 | ||||
|     /// If this type is an instance type where the class has a tuple spec, returns the tuple spec.
 | ||||
|     ///
 | ||||
|     /// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
 | ||||
|     /// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
 | ||||
|     fn tuple_instance_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> { | ||||
|         self.into_nominal_instance() | ||||
|             .and_then(|instance| instance.tuple_spec(db)) | ||||
|     } | ||||
| 
 | ||||
|     /// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
 | ||||
|     /// tuple spec.
 | ||||
|     ///
 | ||||
|     /// You usually don't want to use this method, as you usually want to consider a subclass
 | ||||
|     /// of a tuple type in the same way as the `tuple` type itself. Only use this method if you
 | ||||
|     /// are certain that a *literal tuple* is required, and that a subclass of tuple will not
 | ||||
|     /// do.
 | ||||
|     ///
 | ||||
|     /// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
 | ||||
|     /// But for a subclass of `tuple[int, str]`, it will return `None`.
 | ||||
|     fn exact_tuple_instance_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> { | ||||
|         self.into_nominal_instance() | ||||
|             .and_then(|instance| instance.own_tuple_spec(db)) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the materialization of this type depending on the given `variance`.
 | ||||
|     ///
 | ||||
|     /// More concretely, `T'`, the materialization of `T`, is the type `T` with all occurrences of
 | ||||
|  | @ -747,9 +769,7 @@ impl<'db> Type<'db> { | |||
|                 *self | ||||
|             } | ||||
| 
 | ||||
|             Type::NominalInstance(nominal_instance_type) => { | ||||
|                 Type::NominalInstance(nominal_instance_type.materialize(db, variance)) | ||||
|             } | ||||
|             Type::NominalInstance(instance) => instance.materialize(db, variance), | ||||
|             Type::GenericAlias(generic_alias) => { | ||||
|                 Type::GenericAlias(generic_alias.materialize(db, variance)) | ||||
|             } | ||||
|  | @ -782,7 +802,6 @@ impl<'db> Type<'db> { | |||
|                         .map(|ty| ty.materialize(db, variance.flip())), | ||||
|                 ) | ||||
|                 .build(), | ||||
|             Type::Tuple(tuple_type) => Type::tuple(tuple_type.materialize(db, variance)), | ||||
|             Type::TypeVar(bound_typevar) => Type::TypeVar(bound_typevar.materialize(db, variance)), | ||||
|             Type::TypeIs(type_is) => { | ||||
|                 type_is.with_type(db, type_is.return_type(db).materialize(db, variance)) | ||||
|  | @ -1053,18 +1072,15 @@ impl<'db> Type<'db> { | |||
|             Type::Intersection(intersection) => visitor.visit(self, |v| { | ||||
|                 Type::Intersection(intersection.normalized_impl(db, v)) | ||||
|             }), | ||||
|             Type::Tuple(tuple) => { | ||||
|                 visitor.visit(self, |v| Type::tuple(tuple.normalized_impl(db, v))) | ||||
|             } | ||||
|             Type::Callable(callable) => { | ||||
|                 visitor.visit(self, |v| Type::Callable(callable.normalized_impl(db, v))) | ||||
|             } | ||||
|             Type::ProtocolInstance(protocol) => { | ||||
|                 visitor.visit(self, |v| protocol.normalized_impl(db, v)) | ||||
|             } | ||||
|             Type::NominalInstance(instance) => visitor.visit(self, |v| { | ||||
|                 Type::NominalInstance(instance.normalized_impl(db, v)) | ||||
|             }), | ||||
|             Type::NominalInstance(instance) => { | ||||
|                 visitor.visit(self, |v| instance.normalized_impl(db, v)) | ||||
|             } | ||||
|             Type::FunctionLiteral(function) => visitor.visit(self, |v| { | ||||
|                 Type::FunctionLiteral(function.normalized_impl(db, v)) | ||||
|             }), | ||||
|  | @ -1166,7 +1182,6 @@ impl<'db> Type<'db> { | |||
|             | Type::Union(_) | ||||
|             | Type::Intersection(_) | ||||
|             | Type::Callable(_) | ||||
|             | Type::Tuple(_) | ||||
|             | Type::TypeVar(_) | ||||
|             | Type::BoundSuper(_) | ||||
|             | Type::TypeIs(_) | ||||
|  | @ -1232,7 +1247,6 @@ impl<'db> Type<'db> { | |||
|             | Type::StringLiteral(_) | ||||
|             | Type::LiteralString | ||||
|             | Type::BytesLiteral(_) | ||||
|             | Type::Tuple(_) | ||||
|             | Type::TypeIs(_) | ||||
|             | Type::TypedDict(_) => None, | ||||
| 
 | ||||
|  | @ -1309,7 +1323,7 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|         match (self, target) { | ||||
|             // Everything is a subtype of `object`.
 | ||||
|             (_, Type::NominalInstance(instance)) if instance.class.is_object(db) => true, | ||||
|             (_, Type::NominalInstance(instance)) if instance.is_object(db) => true, | ||||
| 
 | ||||
|             // `Never` is the bottom type, the empty set.
 | ||||
|             // It is a subtype of all other types.
 | ||||
|  | @ -1577,24 +1591,6 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|             (Type::Callable(_), _) => false, | ||||
| 
 | ||||
|             (Type::Tuple(self_tuple), Type::Tuple(target_tuple)) => { | ||||
|                 self_tuple.has_relation_to(db, target_tuple, relation) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Tuple(self_tuple), Type::NominalInstance(target_instance)) => { | ||||
|                 self_tuple.to_class_type(db).is_some_and(|self_class| { | ||||
|                     self_class.has_relation_to(db, target_instance.class, relation) | ||||
|                 }) | ||||
|             } | ||||
|             (Type::NominalInstance(self_instance), Type::Tuple(target_tuple)) => { | ||||
|                 target_tuple.to_class_type(db).is_some_and(|target_class| { | ||||
|                     self_instance | ||||
|                         .class | ||||
|                         .has_relation_to(db, target_class, relation) | ||||
|                 }) | ||||
|             } | ||||
|             (Type::Tuple(_), _) => false, | ||||
| 
 | ||||
|             (Type::BoundSuper(_), Type::BoundSuper(_)) => self.is_equivalent_to(db, target), | ||||
|             (Type::BoundSuper(_), _) => KnownClass::Super | ||||
|                 .to_instance(db) | ||||
|  | @ -1724,8 +1720,6 @@ impl<'db> Type<'db> { | |||
|                 first.is_equivalent_to(db, second) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Tuple(first), Type::Tuple(second)) => first.is_equivalent_to(db, second), | ||||
| 
 | ||||
|             (Type::Union(first), Type::Union(second)) => first.is_equivalent_to(db, second), | ||||
| 
 | ||||
|             (Type::Intersection(first), Type::Intersection(second)) => { | ||||
|  | @ -1748,7 +1742,7 @@ impl<'db> Type<'db> { | |||
|             } | ||||
|             (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | ||||
|             | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) => { | ||||
|                 n.class.is_object(db) && protocol.normalized(db) == nominal | ||||
|                 n.is_object(db) && protocol.normalized(db) == nominal | ||||
|             } | ||||
|             // An instance of an enum class is equivalent to an enum literal of that class,
 | ||||
|             // if that enum has only has one member.
 | ||||
|  | @ -1758,7 +1752,7 @@ impl<'db> Type<'db> { | |||
|                     return false; | ||||
|                 } | ||||
| 
 | ||||
|                 let class_literal = instance.class.class_literal(db).0; | ||||
|                 let class_literal = instance.class(db).class_literal(db).0; | ||||
|                 is_single_member_enum(db, class_literal) | ||||
|             } | ||||
|             _ => false, | ||||
|  | @ -1906,45 +1900,6 @@ impl<'db> Type<'db> { | |||
|                 | Type::KnownInstance(..)), | ||||
|             ) => left != right, | ||||
| 
 | ||||
|             // One tuple type can be a subtype of another tuple type,
 | ||||
|             // but we know for sure that any given tuple type is disjoint from all single-valued types
 | ||||
|             ( | ||||
|                 Type::Tuple(..), | ||||
|                 Type::ClassLiteral(..) | ||||
|                 | Type::GenericAlias(..) | ||||
|                 | Type::ModuleLiteral(..) | ||||
|                 | Type::BooleanLiteral(..) | ||||
|                 | Type::BytesLiteral(..) | ||||
|                 | Type::FunctionLiteral(..) | ||||
|                 | Type::BoundMethod(..) | ||||
|                 | Type::MethodWrapper(..) | ||||
|                 | Type::WrapperDescriptor(..) | ||||
|                 | Type::DataclassDecorator(..) | ||||
|                 | Type::DataclassTransformer(..) | ||||
|                 | Type::IntLiteral(..) | ||||
|                 | Type::EnumLiteral(..) | ||||
|                 | Type::StringLiteral(..) | ||||
|                 | Type::LiteralString, | ||||
|             ) | ||||
|             | ( | ||||
|                 Type::ClassLiteral(..) | ||||
|                 | Type::GenericAlias(..) | ||||
|                 | Type::ModuleLiteral(..) | ||||
|                 | Type::BooleanLiteral(..) | ||||
|                 | Type::BytesLiteral(..) | ||||
|                 | Type::FunctionLiteral(..) | ||||
|                 | Type::BoundMethod(..) | ||||
|                 | Type::MethodWrapper(..) | ||||
|                 | Type::WrapperDescriptor(..) | ||||
|                 | Type::DataclassDecorator(..) | ||||
|                 | Type::DataclassTransformer(..) | ||||
|                 | Type::IntLiteral(..) | ||||
|                 | Type::EnumLiteral(..) | ||||
|                 | Type::StringLiteral(..) | ||||
|                 | Type::LiteralString, | ||||
|                 Type::Tuple(..), | ||||
|             ) => true, | ||||
| 
 | ||||
|             ( | ||||
|                 Type::SubclassOf(_), | ||||
|                 Type::BooleanLiteral(..) | ||||
|  | @ -2058,7 +2013,7 @@ impl<'db> Type<'db> { | |||
|             // (<https://github.com/rust-lang/rust/issues/129967>)
 | ||||
|             (Type::ProtocolInstance(protocol), nominal @ Type::NominalInstance(n)) | ||||
|             | (nominal @ Type::NominalInstance(n), Type::ProtocolInstance(protocol)) | ||||
|                 if n.class.is_final(db) => | ||||
|                 if n.class(db).is_final(db) => | ||||
|             { | ||||
|                 any_protocol_members_absent_or_disjoint(db, protocol, nominal, visitor) | ||||
|             } | ||||
|  | @ -2107,29 +2062,19 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|             (Type::SpecialForm(special_form), Type::NominalInstance(instance)) | ||||
|             | (Type::NominalInstance(instance), Type::SpecialForm(special_form)) => { | ||||
|                 !special_form.is_instance_of(db, instance.class) | ||||
|                 !special_form.is_instance_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::KnownInstance(known_instance), Type::NominalInstance(instance)) | ||||
|             | (Type::NominalInstance(instance), Type::KnownInstance(known_instance)) => { | ||||
|                 !known_instance.is_instance_of(db, instance.class) | ||||
|                 !known_instance.is_instance_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::SpecialForm(special_form), Type::Tuple(tuple)) | ||||
|             | (Type::Tuple(tuple), Type::SpecialForm(special_form)) => tuple | ||||
|                 .to_class_type(db) | ||||
|                 .is_some_and(|tuple_class| !special_form.is_instance_of(db, tuple_class)), | ||||
| 
 | ||||
|             (Type::KnownInstance(known_instance), Type::Tuple(tuple)) | ||||
|             | (Type::Tuple(tuple), Type::KnownInstance(known_instance)) => tuple | ||||
|                 .to_class_type(db) | ||||
|                 .is_some_and(|tuple_class| !known_instance.is_instance_of(db, tuple_class)), | ||||
| 
 | ||||
|             (Type::BooleanLiteral(..) | Type::TypeIs(_), Type::NominalInstance(instance)) | ||||
|             | (Type::NominalInstance(instance), Type::BooleanLiteral(..) | Type::TypeIs(_)) => { | ||||
|                 // A `Type::BooleanLiteral()` must be an instance of exactly `bool`
 | ||||
|                 // (it cannot be an instance of a `bool` subclass)
 | ||||
|                 !KnownClass::Bool.is_subclass_of(db, instance.class) | ||||
|                 !KnownClass::Bool.is_subclass_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::BooleanLiteral(..) | Type::TypeIs(_), _) | ||||
|  | @ -2139,7 +2084,7 @@ impl<'db> Type<'db> { | |||
|             | (Type::NominalInstance(instance), Type::IntLiteral(..)) => { | ||||
|                 // A `Type::IntLiteral()` must be an instance of exactly `int`
 | ||||
|                 // (it cannot be an instance of an `int` subclass)
 | ||||
|                 !KnownClass::Int.is_subclass_of(db, instance.class) | ||||
|                 !KnownClass::Int.is_subclass_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::IntLiteral(..), _) | (_, Type::IntLiteral(..)) => true, | ||||
|  | @ -2151,7 +2096,7 @@ impl<'db> Type<'db> { | |||
|             | (Type::NominalInstance(instance), Type::StringLiteral(..) | Type::LiteralString) => { | ||||
|                 // A `Type::StringLiteral()` or a `Type::LiteralString` must be an instance of exactly `str`
 | ||||
|                 // (it cannot be an instance of a `str` subclass)
 | ||||
|                 !KnownClass::Str.is_subclass_of(db, instance.class) | ||||
|                 !KnownClass::Str.is_subclass_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::LiteralString, Type::LiteralString) => false, | ||||
|  | @ -2161,7 +2106,7 @@ impl<'db> Type<'db> { | |||
|             | (Type::NominalInstance(instance), Type::BytesLiteral(..)) => { | ||||
|                 // A `Type::BytesLiteral()` must be an instance of exactly `bytes`
 | ||||
|                 // (it cannot be an instance of a `bytes` subclass)
 | ||||
|                 !KnownClass::Bytes.is_subclass_of(db, instance.class) | ||||
|                 !KnownClass::Bytes.is_subclass_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::EnumLiteral(enum_literal), instance@Type::NominalInstance(_)) | ||||
|  | @ -2188,7 +2133,7 @@ impl<'db> Type<'db> { | |||
|             | (Type::NominalInstance(instance), Type::FunctionLiteral(..)) => { | ||||
|                 // A `Type::FunctionLiteral()` must be an instance of exactly `types.FunctionType`
 | ||||
|                 // (it cannot be an instance of a `types.FunctionType` subclass)
 | ||||
|                 !KnownClass::FunctionType.is_subclass_of(db, instance.class) | ||||
|                 !KnownClass::FunctionType.is_subclass_of(db, instance.class(db)) | ||||
|             } | ||||
| 
 | ||||
|             (Type::BoundMethod(_), other) | (other, Type::BoundMethod(_)) => KnownClass::MethodType | ||||
|  | @ -2234,12 +2179,12 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|             ( | ||||
|                 Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), | ||||
|                 instance @ Type::NominalInstance(NominalInstanceType { class, .. }), | ||||
|                 instance @ Type::NominalInstance(nominal), | ||||
|             ) | ||||
|             | ( | ||||
|                 instance @ Type::NominalInstance(NominalInstanceType { class, .. }), | ||||
|                 instance @ Type::NominalInstance(nominal), | ||||
|                 Type::Callable(_) | Type::DataclassDecorator(_) | Type::DataclassTransformer(_), | ||||
|             ) if class.is_final(db) => instance | ||||
|             ) if nominal.class(db).is_final(db) => instance | ||||
|                 .member_lookup_with_policy( | ||||
|                     db, | ||||
|                     Name::new_static("__call__"), | ||||
|  | @ -2270,18 +2215,7 @@ impl<'db> Type<'db> { | |||
|             } | ||||
| 
 | ||||
|             (Type::NominalInstance(left), Type::NominalInstance(right)) => { | ||||
|                 left.is_disjoint_from_impl(db, right) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Tuple(tuple), Type::Tuple(other_tuple)) => { | ||||
|                 tuple.is_disjoint_from_impl(db, other_tuple, visitor) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Tuple(tuple), Type::NominalInstance(instance)) | ||||
|             | (Type::NominalInstance(instance), Type::Tuple(tuple)) => { | ||||
|                 tuple.to_class_type(db).is_some_and(|tuple_class| { | ||||
|                     !instance.class.could_coexist_in_mro_with(db, tuple_class) | ||||
|                 }) | ||||
|                 left.is_disjoint_from_impl(db, right, visitor) | ||||
|             } | ||||
| 
 | ||||
|             (Type::PropertyInstance(_), other) | (other, Type::PropertyInstance(_)) => { | ||||
|  | @ -2396,14 +2330,6 @@ impl<'db> Type<'db> { | |||
|             Type::DataclassDecorator(_) | Type::DataclassTransformer(_) => false, | ||||
|             Type::NominalInstance(instance) => instance.is_singleton(db), | ||||
|             Type::PropertyInstance(_) => false, | ||||
|             Type::Tuple(..) => { | ||||
|                 // The empty tuple is a singleton on CPython and PyPy, but not on other Python
 | ||||
|                 // implementations such as GraalPy. Its *use* as a singleton is discouraged and
 | ||||
|                 // should not be relied on for type narrowing, so we do not treat it as one.
 | ||||
|                 // See:
 | ||||
|                 // https://docs.python.org/3/reference/expressions.html#parenthesized-forms
 | ||||
|                 false | ||||
|             } | ||||
|             Type::Union(..) => { | ||||
|                 // A single-element union, where the sole element was a singleton, would itself
 | ||||
|                 // be a singleton type. However, unions with length < 2 should never appear in
 | ||||
|  | @ -2493,7 +2419,6 @@ impl<'db> Type<'db> { | |||
|                 false | ||||
|             } | ||||
| 
 | ||||
|             Type::Tuple(tuple) => tuple.is_single_valued(db), | ||||
|             Type::NominalInstance(instance) => instance.is_single_valued(db), | ||||
| 
 | ||||
|             Type::BoundSuper(_) => { | ||||
|  | @ -2609,7 +2534,9 @@ impl<'db> Type<'db> { | |||
|             // i.e. Type::NominalInstance(type). So looking up a name in the MRO of
 | ||||
|             // `Type::NominalInstance(type)` is equivalent to looking up the name in the
 | ||||
|             // MRO of the class `object`.
 | ||||
|             Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Type) => { | ||||
|             Type::NominalInstance(instance) | ||||
|                 if instance.class(db).is_known(db, KnownClass::Type) => | ||||
|             { | ||||
|                 if policy.mro_no_object_fallback() { | ||||
|                     Some(Place::Unbound.into()) | ||||
|                 } else { | ||||
|  | @ -2637,7 +2564,6 @@ impl<'db> Type<'db> { | |||
|             | Type::LiteralString | ||||
|             | Type::BytesLiteral(_) | ||||
|             | Type::EnumLiteral(_) | ||||
|             | Type::Tuple(_) | ||||
|             | Type::TypeVar(_) | ||||
|             | Type::NominalInstance(_) | ||||
|             | Type::ProtocolInstance(_) | ||||
|  | @ -2724,7 +2650,7 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|             Type::Dynamic(_) | Type::Never => Place::bound(self).into(), | ||||
| 
 | ||||
|             Type::NominalInstance(instance) => instance.class.instance_member(db, name), | ||||
|             Type::NominalInstance(instance) => instance.class(db).instance_member(db, name), | ||||
| 
 | ||||
|             Type::ProtocolInstance(protocol) => protocol.instance_member(db, name), | ||||
| 
 | ||||
|  | @ -2745,12 +2671,12 @@ impl<'db> Type<'db> { | |||
|                 .to_instance(db) | ||||
|                 .instance_member(db, name), | ||||
|             Type::Callable(_) | Type::DataclassTransformer(_) => { | ||||
|                 KnownClass::Object.to_instance(db).instance_member(db, name) | ||||
|                 Type::object(db).instance_member(db, name) | ||||
|             } | ||||
| 
 | ||||
|             Type::TypeVar(bound_typevar) => { | ||||
|                 match bound_typevar.typevar(db).bound_or_constraints(db) { | ||||
|                     None => KnownClass::Object.to_instance(db).instance_member(db, name), | ||||
|                     None => Type::object(db).instance_member(db, name), | ||||
|                     Some(TypeVarBoundOrConstraints::UpperBound(bound)) => { | ||||
|                         bound.instance_member(db, name) | ||||
|                     } | ||||
|  | @ -2772,10 +2698,6 @@ impl<'db> Type<'db> { | |||
|             Type::EnumLiteral(enum_literal) => enum_literal | ||||
|                 .enum_class_instance(db) | ||||
|                 .instance_member(db, name), | ||||
|             Type::Tuple(tuple) => tuple | ||||
|                 .to_class_type(db) | ||||
|                 .map(|class| class.instance_member(db, name)) | ||||
|                 .unwrap_or(Place::Unbound.into()), | ||||
| 
 | ||||
|             Type::AlwaysTruthy | Type::AlwaysFalsy => Type::object(db).instance_member(db, name), | ||||
|             Type::ModuleLiteral(_) => KnownClass::ModuleType | ||||
|  | @ -3235,13 +3157,13 @@ impl<'db> Type<'db> { | |||
|                 .to_instance(db) | ||||
|                 .member_lookup_with_policy(db, name, policy), | ||||
| 
 | ||||
|             Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Object | ||||
|                 .to_instance(db) | ||||
|                 .member_lookup_with_policy(db, name, policy), | ||||
|             Type::Callable(_) | Type::DataclassTransformer(_) => { | ||||
|                 Type::object(db).member_lookup_with_policy(db, name, policy) | ||||
|             } | ||||
| 
 | ||||
|             Type::NominalInstance(instance) | ||||
|                 if matches!(name.as_str(), "major" | "minor") | ||||
|                     && instance.class.is_known(db, KnownClass::VersionInfo) => | ||||
|                     && instance.class(db).is_known(db, KnownClass::VersionInfo) => | ||||
|             { | ||||
|                 let python_version = Program::get(db).python_version(db); | ||||
|                 let segment = if name == "major" { | ||||
|  | @ -3285,7 +3207,6 @@ impl<'db> Type<'db> { | |||
|             | Type::BytesLiteral(..) | ||||
|             | Type::EnumLiteral(..) | ||||
|             | Type::LiteralString | ||||
|             | Type::Tuple(..) | ||||
|             | Type::TypeVar(..) | ||||
|             | Type::SpecialForm(..) | ||||
|             | Type::KnownInstance(..) | ||||
|  | @ -3315,7 +3236,7 @@ impl<'db> Type<'db> { | |||
|                     // resolve the attribute.
 | ||||
|                     if matches!( | ||||
|                         self.into_nominal_instance() | ||||
|                             .and_then(|instance| instance.class.known(db)), | ||||
|                             .and_then(|instance| instance.class(db).known(db)), | ||||
|                         Some(KnownClass::ModuleType | KnownClass::GenericAlias) | ||||
|                     ) { | ||||
|                         return Place::Unbound.into(); | ||||
|  | @ -3633,10 +3554,12 @@ impl<'db> Type<'db> { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             Type::NominalInstance(instance) => match instance.class.known(db) { | ||||
|                 Some(known_class) => known_class.bool(), | ||||
|                 None => try_dunder_bool()?, | ||||
|             }, | ||||
|             Type::NominalInstance(instance) => instance | ||||
|                 .class(db) | ||||
|                 .known(db) | ||||
|                 .and_then(KnownClass::bool) | ||||
|                 .map(Ok) | ||||
|                 .unwrap_or_else(try_dunder_bool)?, | ||||
| 
 | ||||
|             Type::ProtocolInstance(_) => try_dunder_bool()?, | ||||
| 
 | ||||
|  | @ -3657,7 +3580,6 @@ impl<'db> Type<'db> { | |||
|             Type::BooleanLiteral(bool) => Truthiness::from(*bool), | ||||
|             Type::StringLiteral(str) => Truthiness::from(!str.value(db).is_empty()), | ||||
|             Type::BytesLiteral(bytes) => Truthiness::from(!bytes.value(db).is_empty()), | ||||
|             Type::Tuple(tuple) => tuple.truthiness(db), | ||||
|         }; | ||||
| 
 | ||||
|         Ok(truthiness) | ||||
|  | @ -3684,13 +3606,6 @@ impl<'db> Type<'db> { | |||
|         let usize_len = match self { | ||||
|             Type::BytesLiteral(bytes) => Some(bytes.python_len(db)), | ||||
|             Type::StringLiteral(string) => Some(string.python_len(db)), | ||||
| 
 | ||||
|             // N.B. This is strictly-speaking redundant, since the `__len__` method on tuples
 | ||||
|             // is special-cased in `ClassType::own_class_member`. However, it's probably more
 | ||||
|             // efficient to short-circuit here and check against the tuple spec directly,
 | ||||
|             // rather than going through the `__len__` method.
 | ||||
|             Type::Tuple(tuple) => tuple.tuple(db).len().into_fixed_length(), | ||||
| 
 | ||||
|             _ => None, | ||||
|         }; | ||||
| 
 | ||||
|  | @ -4236,10 +4151,7 @@ impl<'db> Type<'db> { | |||
|                     // ```
 | ||||
|                     Binding::single( | ||||
|                         self, | ||||
|                         Signature::new( | ||||
|                             Parameters::empty(), | ||||
|                             Some(KnownClass::Object.to_instance(db)), | ||||
|                         ), | ||||
|                         Signature::new(Parameters::empty(), Some(Type::object(db))), | ||||
|                     ) | ||||
|                     .into() | ||||
|                 } | ||||
|  | @ -4633,7 +4545,6 @@ impl<'db> Type<'db> { | |||
|             | Type::BytesLiteral(_) | ||||
|             | Type::BooleanLiteral(_) | ||||
|             | Type::LiteralString | ||||
|             | Type::Tuple(_) | ||||
|             | Type::BoundSuper(_) | ||||
|             | Type::ModuleLiteral(_) | ||||
|             | Type::TypeIs(_) | ||||
|  | @ -4791,7 +4702,11 @@ impl<'db> Type<'db> { | |||
|         } | ||||
| 
 | ||||
|         match self { | ||||
|             Type::Tuple(tuple_type) => return Ok(Cow::Borrowed(tuple_type.tuple(db))), | ||||
|             Type::NominalInstance(nominal) => { | ||||
|                 if let Some(spec) = nominal.tuple_spec(db) { | ||||
|                     return Ok(spec); | ||||
|                 } | ||||
|             } | ||||
|             Type::GenericAlias(alias) if alias.origin(db).is_tuple(db) => { | ||||
|                 return Ok(Cow::Owned(TupleSpec::homogeneous(todo_type!( | ||||
|                     "*tuple[] annotations" | ||||
|  | @ -5017,7 +4932,7 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|         match self { | ||||
|             Type::NominalInstance(instance) => { | ||||
|                 instance.class.iter_mro(db).find_map(from_class_base) | ||||
|                 instance.class(db).iter_mro(db).find_map(from_class_base) | ||||
|             } | ||||
|             Type::ProtocolInstance(instance) => { | ||||
|                 if let Protocol::FromClass(class) = instance.inner { | ||||
|  | @ -5276,7 +5191,6 @@ impl<'db> Type<'db> { | |||
|             | Type::ModuleLiteral(_) | ||||
|             | Type::IntLiteral(_) | ||||
|             | Type::StringLiteral(_) | ||||
|             | Type::Tuple(_) | ||||
|             | Type::LiteralString | ||||
|             | Type::BoundSuper(_) | ||||
|             | Type::AlwaysTruthy | ||||
|  | @ -5344,7 +5258,6 @@ impl<'db> Type<'db> { | |||
|             | Type::LiteralString | ||||
|             | Type::ModuleLiteral(_) | ||||
|             | Type::StringLiteral(_) | ||||
|             | Type::Tuple(_) | ||||
|             | Type::TypeVar(_) | ||||
|             | Type::Callable(_) | ||||
|             | Type::BoundMethod(_) | ||||
|  | @ -5557,7 +5470,7 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
|             Type::Dynamic(_) => Ok(*self), | ||||
| 
 | ||||
|             Type::NominalInstance(instance) => match instance.class.known(db) { | ||||
|             Type::NominalInstance(instance) => match instance.class(db).known(db) { | ||||
|                 Some(KnownClass::TypeVar) => Ok(todo_type!( | ||||
|                     "Support for `typing.TypeVar` instances in type expressions" | ||||
|                 )), | ||||
|  | @ -5593,40 +5506,6 @@ impl<'db> Type<'db> { | |||
|         KnownClass::NoneType.to_instance(db) | ||||
|     } | ||||
| 
 | ||||
|     /// Return the type of `tuple(sys.version_info)`.
 | ||||
|     ///
 | ||||
|     /// This is not exactly the type that `sys.version_info` has at runtime,
 | ||||
|     /// but it's a useful fallback for us in order to infer `Literal` types from `sys.version_info` comparisons.
 | ||||
|     fn version_info_tuple(db: &'db dyn Db) -> Self { | ||||
|         let python_version = Program::get(db).python_version(db); | ||||
|         let int_instance_ty = KnownClass::Int.to_instance(db); | ||||
| 
 | ||||
|         // TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
 | ||||
|         let release_level_ty = { | ||||
|             let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"] | ||||
|                 .iter() | ||||
|                 .map(|level| Type::string_literal(db, level)) | ||||
|                 .collect(); | ||||
| 
 | ||||
|             // For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
 | ||||
|             // those techniques ensure that union elements are deduplicated and unions are eagerly simplified
 | ||||
|             // into other types where necessary. Here, however, we know that there are no duplicates
 | ||||
|             // in this union, so it's probably more efficient to use `UnionType::new()` directly.
 | ||||
|             Type::Union(UnionType::new(db, elements)) | ||||
|         }; | ||||
| 
 | ||||
|         Type::heterogeneous_tuple( | ||||
|             db, | ||||
|             [ | ||||
|                 Type::IntLiteral(python_version.major.into()), | ||||
|                 Type::IntLiteral(python_version.minor.into()), | ||||
|                 int_instance_ty, | ||||
|                 release_level_ty, | ||||
|                 int_instance_ty, | ||||
|             ], | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     /// Given a type that is assumed to represent an instance of a class,
 | ||||
|     /// return a type that represents that class itself.
 | ||||
|     ///
 | ||||
|  | @ -5655,9 +5534,6 @@ impl<'db> Type<'db> { | |||
|             } | ||||
|             Type::Callable(_) | Type::DataclassTransformer(_) => KnownClass::Type.to_instance(db), | ||||
|             Type::ModuleLiteral(_) => KnownClass::ModuleType.to_class_literal(db), | ||||
|             Type::Tuple(tuple) => tuple | ||||
|                 .to_subclass_of(db) | ||||
|                 .unwrap_or_else(SubclassOfType::subclass_of_unknown), | ||||
|             Type::TypeVar(bound_typevar) => { | ||||
|                 match bound_typevar.typevar(db).bound_or_constraints(db) { | ||||
|                     None => KnownClass::Type.to_instance(db), | ||||
|  | @ -5777,9 +5653,8 @@ impl<'db> Type<'db> { | |||
|                 method.self_instance(db).apply_type_mapping(db, type_mapping), | ||||
|             )), | ||||
| 
 | ||||
|             Type::NominalInstance(instance) => Type::NominalInstance( | ||||
|             Type::NominalInstance(instance) => | ||||
|                 instance.apply_type_mapping(db, type_mapping), | ||||
|             ), | ||||
| 
 | ||||
|             Type::ProtocolInstance(instance) => { | ||||
|                 Type::ProtocolInstance(instance.apply_type_mapping(db, type_mapping)) | ||||
|  | @ -5844,7 +5719,6 @@ impl<'db> Type<'db> { | |||
|                 } | ||||
|                 builder.build() | ||||
|             } | ||||
|             Type::Tuple(tuple) => Type::tuple(tuple.apply_type_mapping(db, type_mapping)), | ||||
| 
 | ||||
|             Type::TypeIs(type_is) => type_is.with_type(db, type_is.return_type(db).apply_type_mapping(db, type_mapping)), | ||||
| 
 | ||||
|  | @ -5950,10 +5824,6 @@ impl<'db> Type<'db> { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             Type::Tuple(tuple) => { | ||||
|                 tuple.find_legacy_typevars(db, binding_context, typevars); | ||||
|             } | ||||
| 
 | ||||
|             Type::GenericAlias(alias) => { | ||||
|                 alias.find_legacy_typevars(db, binding_context, typevars); | ||||
|             } | ||||
|  | @ -6071,7 +5941,7 @@ impl<'db> Type<'db> { | |||
|             } | ||||
|             Self::GenericAlias(alias) => Some(TypeDefinition::Class(alias.definition(db))), | ||||
|             Self::NominalInstance(instance) => { | ||||
|                 Some(TypeDefinition::Class(instance.class.definition(db))) | ||||
|                 Some(TypeDefinition::Class(instance.class(db).definition(db))) | ||||
|             } | ||||
|             Self::KnownInstance(instance) => match instance { | ||||
|                 KnownInstanceType::TypeVar(var) => { | ||||
|  | @ -6100,8 +5970,7 @@ impl<'db> Type<'db> { | |||
|             | Self::DataclassDecorator(_) | ||||
|             | Self::DataclassTransformer(_) | ||||
|             | Self::PropertyInstance(_) | ||||
|             | Self::BoundSuper(_) | ||||
|             | Self::Tuple(_) => self.to_meta_type(db).definition(db), | ||||
|             | Self::BoundSuper(_) => self.to_meta_type(db).definition(db), | ||||
| 
 | ||||
|             Self::TypeVar(bound_typevar) => Some(TypeDefinition::TypeVar(bound_typevar.typevar(db).definition(db)?)), | ||||
| 
 | ||||
|  | @ -6193,7 +6062,7 @@ impl<'db> Type<'db> { | |||
|         match self { | ||||
|             Type::GenericAlias(generic) => Some(generic.origin(db)), | ||||
|             Type::NominalInstance(instance) => { | ||||
|                 if let ClassType::Generic(generic) = instance.class { | ||||
|                 if let ClassType::Generic(generic) = instance.class(db) { | ||||
|                     Some(generic.origin(db)) | ||||
|                 } else { | ||||
|                     None | ||||
|  | @ -9280,9 +9149,11 @@ impl<'db> SuperOwnerKind<'db> { | |||
|             SuperOwnerKind::Class(class) => { | ||||
|                 SuperOwnerKind::Class(class.normalized_impl(db, visitor)) | ||||
|             } | ||||
|             SuperOwnerKind::Instance(instance) => { | ||||
|                 SuperOwnerKind::Instance(instance.normalized_impl(db, visitor)) | ||||
|             } | ||||
|             SuperOwnerKind::Instance(instance) => instance | ||||
|                 .normalized_impl(db, visitor) | ||||
|                 .into_nominal_instance() | ||||
|                 .map(Self::Instance) | ||||
|                 .unwrap_or(Self::Dynamic(DynamicType::Any)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -9292,7 +9163,7 @@ impl<'db> SuperOwnerKind<'db> { | |||
|                 Either::Left(ClassBase::Dynamic(dynamic).mro(db, None)) | ||||
|             } | ||||
|             SuperOwnerKind::Class(class) => Either::Right(class.iter_mro(db)), | ||||
|             SuperOwnerKind::Instance(instance) => Either::Right(instance.class.iter_mro(db)), | ||||
|             SuperOwnerKind::Instance(instance) => Either::Right(instance.class(db).iter_mro(db)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -9304,11 +9175,11 @@ impl<'db> SuperOwnerKind<'db> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn into_class(self) -> Option<ClassType<'db>> { | ||||
|     fn into_class(self, db: &'db dyn Db) -> Option<ClassType<'db>> { | ||||
|         match self { | ||||
|             SuperOwnerKind::Dynamic(_) => None, | ||||
|             SuperOwnerKind::Class(class) => Some(class), | ||||
|             SuperOwnerKind::Instance(instance) => Some(instance.class), | ||||
|             SuperOwnerKind::Instance(instance) => Some(instance.class(db)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -9406,7 +9277,7 @@ impl<'db> BoundSuperType<'db> { | |||
|                 let Some(pivot_class) = pivot_class.into_class() else { | ||||
|                     return Some(owner); | ||||
|                 }; | ||||
|                 let Some(owner_class) = owner.into_class() else { | ||||
|                 let Some(owner_class) = owner.into_class(db) else { | ||||
|                     return Some(owner); | ||||
|                 }; | ||||
|                 if owner_class.is_subclass_of(db, pivot_class) { | ||||
|  | @ -9507,7 +9378,7 @@ impl<'db> BoundSuperType<'db> { | |||
|                     .expect("Calling `find_name_in_mro` on dynamic type should return `Some`"); | ||||
|             } | ||||
|             SuperOwnerKind::Class(class) => class, | ||||
|             SuperOwnerKind::Instance(instance) => instance.class, | ||||
|             SuperOwnerKind::Instance(instance) => instance.class(db), | ||||
|         }; | ||||
| 
 | ||||
|         let (class_literal, _) = class.class_literal(db); | ||||
|  |  | |||
|  | @ -572,7 +572,8 @@ impl<'db> IntersectionBuilder<'db> { | |||
|                 self | ||||
|             } | ||||
|             Type::NominalInstance(instance) | ||||
|                 if enum_metadata(self.db, instance.class.class_literal(self.db).0).is_some() => | ||||
|                 if enum_metadata(self.db, instance.class(self.db).class_literal(self.db).0) | ||||
|                     .is_some() => | ||||
|             { | ||||
|                 let mut contains_enum_literal_as_negative_element = false; | ||||
|                 for intersection in &self.intersections { | ||||
|  | @ -596,7 +597,7 @@ impl<'db> IntersectionBuilder<'db> { | |||
|                     let db = self.db; | ||||
|                     self.add_positive(Type::Union(UnionType::new( | ||||
|                         db, | ||||
|                         enum_member_literals(db, instance.class.class_literal(db).0, None) | ||||
|                         enum_member_literals(db, instance.class(db).class_literal(db).0, None) | ||||
|                             .expect("Calling `enum_member_literals` on an enum class") | ||||
|                             .collect::<Box<[_]>>(), | ||||
|                     ))) | ||||
|  | @ -762,7 +763,7 @@ impl<'db> InnerIntersectionBuilder<'db> { | |||
|             _ => { | ||||
|                 let known_instance = new_positive | ||||
|                     .into_nominal_instance() | ||||
|                     .and_then(|instance| instance.class.known(db)); | ||||
|                     .and_then(|instance| instance.class(db).known(db)); | ||||
| 
 | ||||
|                 if known_instance == Some(KnownClass::Object) { | ||||
|                     // `object & T` -> `T`; it is always redundant to add `object` to an intersection
 | ||||
|  | @ -782,7 +783,7 @@ impl<'db> InnerIntersectionBuilder<'db> { | |||
|                             new_positive = Type::BooleanLiteral(false); | ||||
|                         } | ||||
|                         Type::NominalInstance(instance) | ||||
|                             if instance.class.is_known(db, KnownClass::Bool) => | ||||
|                             if instance.class(db).is_known(db, KnownClass::Bool) => | ||||
|                         { | ||||
|                             match new_positive { | ||||
|                                 // `bool & AlwaysTruthy` -> `Literal[True]`
 | ||||
|  | @ -876,7 +877,7 @@ impl<'db> InnerIntersectionBuilder<'db> { | |||
|             self.positive | ||||
|                 .iter() | ||||
|                 .filter_map(|ty| ty.into_nominal_instance()) | ||||
|                 .filter_map(|instance| instance.class.known(db)) | ||||
|                 .filter_map(|instance| instance.class(db).known(db)) | ||||
|                 .any(KnownClass::is_bool) | ||||
|         }; | ||||
| 
 | ||||
|  | @ -892,7 +893,7 @@ impl<'db> InnerIntersectionBuilder<'db> { | |||
|             Type::Never => { | ||||
|                 // Adding ~Never to an intersection is a no-op.
 | ||||
|             } | ||||
|             Type::NominalInstance(instance) if instance.class.is_object(db) => { | ||||
|             Type::NominalInstance(instance) if instance.is_object(db) => { | ||||
|                 // Adding ~object to an intersection results in Never.
 | ||||
|                 *self = Self::default(); | ||||
|                 self.positive.insert(Type::Never); | ||||
|  |  | |||
|  | @ -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}; | ||||
| use crate::types::tuple::{Tuple, TupleLength, TupleType}; | ||||
| 
 | ||||
| use super::Type; | ||||
| 
 | ||||
|  | @ -214,47 +214,49 @@ impl<'a, 'db> FromIterator<(Argument<'a>, Option<Type<'db>>)> for CallArguments< | |||
| fn expand_type<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Vec<Type<'db>>> { | ||||
|     match ty { | ||||
|         Type::NominalInstance(instance) => { | ||||
|             if instance.class.is_known(db, KnownClass::Bool) { | ||||
|             let class = instance.class(db); | ||||
| 
 | ||||
|             if class.is_known(db, KnownClass::Bool) { | ||||
|                 return Some(vec![ | ||||
|                     Type::BooleanLiteral(true), | ||||
|                     Type::BooleanLiteral(false), | ||||
|                 ]); | ||||
|             } | ||||
| 
 | ||||
|             let class_literal = instance.class.class_literal(db).0; | ||||
|             // If the class is a fixed-length tuple subtype, we expand it to its elements.
 | ||||
|             if let Some(spec) = instance.tuple_spec(db) { | ||||
|                 return match &*spec { | ||||
|                     Tuple::Fixed(fixed_length_tuple) => { | ||||
|                         let expanded = fixed_length_tuple | ||||
|                             .all_elements() | ||||
|                             .map(|element| { | ||||
|                                 if let Some(expanded) = expand_type(db, *element) { | ||||
|                                     Either::Left(expanded.into_iter()) | ||||
|                                 } else { | ||||
|                                     Either::Right(std::iter::once(*element)) | ||||
|                                 } | ||||
|                             }) | ||||
|                             .multi_cartesian_product() | ||||
|                             .map(|types| Type::tuple(TupleType::from_elements(db, types))) | ||||
|                             .collect::<Vec<_>>(); | ||||
| 
 | ||||
|             if let Some(enum_members) = enum_member_literals(db, class_literal, None) { | ||||
|                         if expanded.len() == 1 { | ||||
|                             // There are no elements in the tuple type that can be expanded.
 | ||||
|                             None | ||||
|                         } else { | ||||
|                             Some(expanded) | ||||
|                         } | ||||
|                     } | ||||
|                     Tuple::Variable(_) => None, | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             if let Some(enum_members) = enum_member_literals(db, class.class_literal(db).0, None) { | ||||
|                 return Some(enum_members.collect()); | ||||
|             } | ||||
| 
 | ||||
|             None | ||||
|         } | ||||
|         Type::Tuple(tuple_type) => { | ||||
|             // Note: This should only account for tuples of known length, i.e., `tuple[bool, ...]`
 | ||||
|             // should not be expanded here.
 | ||||
|             let tuple = tuple_type.tuple(db); | ||||
|             if !matches!(tuple, TupleSpec::Fixed(_)) { | ||||
|                 return None; | ||||
|             } | ||||
|             let expanded = tuple | ||||
|                 .all_elements() | ||||
|                 .map(|element| { | ||||
|                     if let Some(expanded) = expand_type(db, *element) { | ||||
|                         Either::Left(expanded.into_iter()) | ||||
|                     } else { | ||||
|                         Either::Right(std::iter::once(*element)) | ||||
|                     } | ||||
|                 }) | ||||
|                 .multi_cartesian_product() | ||||
|                 .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.
 | ||||
|                 None | ||||
|             } else { | ||||
|                 Some(expanded) | ||||
|             } | ||||
|         } | ||||
|         Type::Union(union) => Some(union.iter(db).copied().collect()), | ||||
|         // We don't handle `type[A | B]` here because it's already stored in the expanded form
 | ||||
|         // i.e., `type[A] | type[B]` which is handled by the `Type::Union` case.
 | ||||
|  |  | |||
|  | @ -273,8 +273,6 @@ impl<'db> GenericAlias<'db> { | |||
|         binding_context: Option<Definition<'db>>, | ||||
|         typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>, | ||||
|     ) { | ||||
|         // A tuple's specialization will include all of its element types, so we don't need to also
 | ||||
|         // look in `self.tuple`.
 | ||||
|         self.specialization(db) | ||||
|             .find_legacy_typevars(db, binding_context, typevars); | ||||
|     } | ||||
|  | @ -316,6 +314,13 @@ impl<'db> ClassType<'db> { | |||
|         matches!(self, Self::NonGeneric(_)) | ||||
|     } | ||||
| 
 | ||||
|     pub(super) const fn into_generic_alias(self) -> Option<GenericAlias<'db>> { | ||||
|         match self { | ||||
|             Self::NonGeneric(_) => None, | ||||
|             Self::Generic(generic) => Some(generic), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn normalized_impl( | ||||
|         self, | ||||
|         db: &'db dyn Db, | ||||
|  | @ -663,7 +668,8 @@ impl<'db> ClassType<'db> { | |||
|         match name { | ||||
|             "__len__" if class_literal.is_tuple(db) => { | ||||
|                 let return_type = specialization | ||||
|                     .and_then(|spec| spec.tuple(db).len().into_fixed_length()) | ||||
|                     .and_then(|spec| spec.tuple(db)) | ||||
|                     .and_then(|tuple| tuple.len().into_fixed_length()) | ||||
|                     .and_then(|len| i64::try_from(len).ok()) | ||||
|                     .map(Type::IntLiteral) | ||||
|                     .unwrap_or_else(|| KnownClass::Int.to_instance(db)); | ||||
|  | @ -673,7 +679,8 @@ impl<'db> ClassType<'db> { | |||
| 
 | ||||
|             "__bool__" if class_literal.is_tuple(db) => { | ||||
|                 let return_type = specialization | ||||
|                     .map(|spec| spec.tuple(db).truthiness().into_type(db)) | ||||
|                     .and_then(|spec| spec.tuple(db)) | ||||
|                     .map(|tuple| tuple.truthiness().into_type(db)) | ||||
|                     .unwrap_or_else(|| KnownClass::Bool.to_instance(db)); | ||||
| 
 | ||||
|                 synthesize_simple_tuple_method(return_type) | ||||
|  | @ -681,9 +688,8 @@ impl<'db> ClassType<'db> { | |||
| 
 | ||||
|             "__getitem__" if class_literal.is_tuple(db) => { | ||||
|                 specialization | ||||
|                     .map(|spec| { | ||||
|                         let tuple = spec.tuple(db); | ||||
| 
 | ||||
|                     .and_then(|spec| spec.tuple(db)) | ||||
|                     .map(|tuple| { | ||||
|                         let mut element_type_to_indices: FxIndexMap<Type<'db>, Vec<i64>> = | ||||
|                             FxIndexMap::default(); | ||||
| 
 | ||||
|  | @ -846,11 +852,12 @@ impl<'db> ClassType<'db> { | |||
|                 let mut iterable_parameter = | ||||
|                     Parameter::positional_only(Some(Name::new_static("iterable"))); | ||||
| 
 | ||||
|                 match specialization { | ||||
|                     Some(spec) => { | ||||
|                 let tuple = specialization.and_then(|spec| spec.tuple(db)); | ||||
| 
 | ||||
|                 match tuple { | ||||
|                     Some(tuple) => { | ||||
|                         // TODO: Once we support PEP 646 annotations for `*args` parameters, we can
 | ||||
|                         // use the tuple itself as the argument type.
 | ||||
|                         let tuple = spec.tuple(db); | ||||
|                         let tuple_len = tuple.len(); | ||||
| 
 | ||||
|                         if tuple_len.minimum() == 0 && tuple_len.maximum().is_none() { | ||||
|  | @ -885,7 +892,7 @@ impl<'db> ClassType<'db> { | |||
|                 // - a zero-length tuple
 | ||||
|                 // - an unspecialized tuple
 | ||||
|                 // - a tuple with no minimum length
 | ||||
|                 if specialization.is_none_or(|spec| spec.tuple(db).len().minimum() == 0) { | ||||
|                 if tuple.is_none_or(|tuple| tuple.len().minimum() == 0) { | ||||
|                     iterable_parameter = | ||||
|                         iterable_parameter.with_default_type(Type::empty_tuple(db)); | ||||
|                 } | ||||
|  | @ -1227,7 +1234,7 @@ impl<'db> ClassLiteral<'db> { | |||
|         let parsed = parsed_module(db, file).load(db); | ||||
|         let class_def_node = scope.node(db).expect_class(&parsed); | ||||
|         class_def_node.type_params.as_ref().map(|type_params| { | ||||
|             let index = semantic_index(db, file); | ||||
|             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) | ||||
|         }) | ||||
|  | @ -1297,7 +1304,8 @@ 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)) | ||||
|             specialization | ||||
|                 .unwrap_or_else(|| generic_context.default_specialization(db, self.known(db))) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|  | @ -1306,7 +1314,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) | ||||
|             generic_context.default_specialization(db, self.known(db)) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|  | @ -1887,7 +1895,7 @@ impl<'db> ClassLiteral<'db> { | |||
| 
 | ||||
|                 if field_ty | ||||
|                     .into_nominal_instance() | ||||
|                     .is_some_and(|instance| instance.class.is_known(db, KnownClass::KwOnly)) | ||||
|                     .is_some_and(|instance| instance.class(db).is_known(db, KnownClass::KwOnly)) | ||||
|                 { | ||||
|                     // Attributes annotated with `dataclass.KW_ONLY` are not present in the synthesized
 | ||||
|                     // `__init__` method; they are used to indicate that the following parameters are
 | ||||
|  | @ -3100,7 +3108,10 @@ impl KnownClass { | |||
| 
 | ||||
|     /// Determine whether instances of this class are always truthy, always falsy,
 | ||||
|     /// or have an ambiguous truthiness.
 | ||||
|     pub(crate) const fn bool(self) -> Truthiness { | ||||
|     ///
 | ||||
|     /// Returns `None` for `KnownClass::Tuple`, since the truthiness of a tuple
 | ||||
|     /// depends on its spec.
 | ||||
|     pub(crate) const fn bool(self) -> Option<Truthiness> { | ||||
|         match self { | ||||
|             // N.B. It's only generally safe to infer `Truthiness::AlwaysTrue` for a `KnownClass`
 | ||||
|             // variant if the class's `__bool__` method always returns the same thing *and* the
 | ||||
|  | @ -3126,9 +3137,9 @@ impl KnownClass { | |||
|             | Self::GeneratorType | ||||
|             | Self::AsyncGeneratorType | ||||
|             | Self::MethodWrapperType | ||||
|             | Self::CoroutineType => Truthiness::AlwaysTrue, | ||||
|             | Self::CoroutineType => Some(Truthiness::AlwaysTrue), | ||||
| 
 | ||||
|             Self::NoneType => Truthiness::AlwaysFalse, | ||||
|             Self::NoneType => Some(Truthiness::AlwaysFalse), | ||||
| 
 | ||||
|             Self::Any | ||||
|             | Self::BaseException | ||||
|  | @ -3145,7 +3156,6 @@ impl KnownClass { | |||
|             | Self::StdlibAlias | ||||
|             | Self::SupportsIndex | ||||
|             | Self::Set | ||||
|             | Self::Tuple | ||||
|             | Self::Int | ||||
|             | Self::Type | ||||
|             | Self::Bytes | ||||
|  | @ -3184,7 +3194,9 @@ impl KnownClass { | |||
|             | Self::KwOnly | ||||
|             | Self::InitVar | ||||
|             | Self::NamedTupleFallback | ||||
|             | Self::TypedDictFallback => Truthiness::Ambiguous, | ||||
|             | Self::TypedDictFallback => Some(Truthiness::Ambiguous), | ||||
| 
 | ||||
|             Self::Tuple => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -3432,6 +3444,82 @@ impl KnownClass { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) const fn is_tuple_subclass(self) -> bool { | ||||
|         match self { | ||||
|             KnownClass::Tuple | KnownClass::VersionInfo => true, | ||||
| 
 | ||||
|             KnownClass::Bool | ||||
|             | KnownClass::Object | ||||
|             | KnownClass::Bytes | ||||
|             | KnownClass::Bytearray | ||||
|             | KnownClass::Type | ||||
|             | KnownClass::Int | ||||
|             | KnownClass::Float | ||||
|             | KnownClass::Complex | ||||
|             | KnownClass::Str | ||||
|             | KnownClass::List | ||||
|             | KnownClass::Set | ||||
|             | KnownClass::FrozenSet | ||||
|             | KnownClass::Dict | ||||
|             | KnownClass::Slice | ||||
|             | KnownClass::Property | ||||
|             | KnownClass::BaseException | ||||
|             | KnownClass::Exception | ||||
|             | KnownClass::BaseExceptionGroup | ||||
|             | KnownClass::ExceptionGroup | ||||
|             | KnownClass::Staticmethod | ||||
|             | KnownClass::Classmethod | ||||
|             | KnownClass::Awaitable | ||||
|             | KnownClass::Generator | ||||
|             | KnownClass::Deprecated | ||||
|             | KnownClass::Super | ||||
|             | KnownClass::Enum | ||||
|             | KnownClass::EnumType | ||||
|             | KnownClass::Auto | ||||
|             | KnownClass::Member | ||||
|             | KnownClass::Nonmember | ||||
|             | KnownClass::ABCMeta | ||||
|             | KnownClass::GenericAlias | ||||
|             | KnownClass::ModuleType | ||||
|             | KnownClass::FunctionType | ||||
|             | KnownClass::MethodType | ||||
|             | KnownClass::MethodWrapperType | ||||
|             | KnownClass::WrapperDescriptorType | ||||
|             | KnownClass::UnionType | ||||
|             | KnownClass::GeneratorType | ||||
|             | KnownClass::AsyncGeneratorType | ||||
|             | KnownClass::CoroutineType | ||||
|             | KnownClass::NoneType | ||||
|             | KnownClass::Any | ||||
|             | KnownClass::StdlibAlias | ||||
|             | KnownClass::SpecialForm | ||||
|             | KnownClass::TypeVar | ||||
|             | KnownClass::ParamSpec | ||||
|             | KnownClass::ParamSpecArgs | ||||
|             | KnownClass::ParamSpecKwargs | ||||
|             | KnownClass::TypeVarTuple | ||||
|             | KnownClass::TypeAliasType | ||||
|             | KnownClass::NoDefaultType | ||||
|             | KnownClass::NamedTuple | ||||
|             | KnownClass::NewType | ||||
|             | KnownClass::SupportsIndex | ||||
|             | KnownClass::Iterable | ||||
|             | KnownClass::Iterator | ||||
|             | KnownClass::ChainMap | ||||
|             | KnownClass::Counter | ||||
|             | KnownClass::DefaultDict | ||||
|             | KnownClass::Deque | ||||
|             | KnownClass::OrderedDict | ||||
|             | KnownClass::EllipsisType | ||||
|             | KnownClass::NotImplementedType | ||||
|             | KnownClass::Field | ||||
|             | KnownClass::KwOnly | ||||
|             | KnownClass::InitVar | ||||
|             | KnownClass::TypedDictFallback | ||||
|             | KnownClass::NamedTupleFallback => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Return `true` if this class is a protocol class.
 | ||||
|     ///
 | ||||
|     /// In an ideal world, perhaps we wouldn't hardcode this knowledge here;
 | ||||
|  | @ -3874,8 +3962,10 @@ impl KnownClass { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Return true if all instances of this `KnownClass` compare equal.
 | ||||
|     pub(super) const fn is_single_valued(self) -> bool { | ||||
|     /// Returns `Some(true)` if all instances of this `KnownClass` compare equal.
 | ||||
|     /// Returns `None` for `KnownClass::Tuple`, since whether or not a tuple type
 | ||||
|     /// is single-valued depends on the tuple spec.
 | ||||
|     pub(super) const fn is_single_valued(self) -> Option<bool> { | ||||
|         match self { | ||||
|             Self::NoneType | ||||
|             | Self::NoDefaultType | ||||
|  | @ -3883,7 +3973,7 @@ impl KnownClass { | |||
|             | Self::EllipsisType | ||||
|             | Self::TypeAliasType | ||||
|             | Self::UnionType | ||||
|             | Self::NotImplementedType => true, | ||||
|             | Self::NotImplementedType => Some(true), | ||||
| 
 | ||||
|             Self::Any | ||||
|             | Self::Bool | ||||
|  | @ -3896,7 +3986,6 @@ impl KnownClass { | |||
|             | Self::Complex | ||||
|             | Self::Str | ||||
|             | Self::List | ||||
|             | Self::Tuple | ||||
|             | Self::Set | ||||
|             | Self::FrozenSet | ||||
|             | Self::Dict | ||||
|  | @ -3948,7 +4037,9 @@ impl KnownClass { | |||
|             | Self::Iterable | ||||
|             | Self::Iterator | ||||
|             | Self::NamedTupleFallback | ||||
|             | Self::TypedDictFallback => false, | ||||
|             | Self::TypedDictFallback => Some(false), | ||||
| 
 | ||||
|             Self::Tuple => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -4555,47 +4646,6 @@ impl<'db> KnownClassLookupError<'db> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| pub(crate) struct SliceLiteral { | ||||
|     pub(crate) start: Option<i32>, | ||||
|     pub(crate) stop: Option<i32>, | ||||
|     pub(crate) step: Option<i32>, | ||||
| } | ||||
| 
 | ||||
| impl<'db> ClassType<'db> { | ||||
|     /// If this class is a specialization of `slice`, returns a [`SliceLiteral`] describing it.
 | ||||
|     /// Otherwise returns `None`.
 | ||||
|     ///
 | ||||
|     /// The specialization must be one in which the typevars are solved as being statically known
 | ||||
|     /// integers or `None`.
 | ||||
|     pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> { | ||||
|         let ClassType::Generic(alias) = self else { | ||||
|             return None; | ||||
|         }; | ||||
|         if !alias.origin(db).is_known(db, KnownClass::Slice) { | ||||
|             return None; | ||||
|         } | ||||
|         let [start, stop, step] = alias.specialization(db).types(db) else { | ||||
|             return None; | ||||
|         }; | ||||
| 
 | ||||
|         let to_u32 = |ty: &Type<'db>| match ty { | ||||
|             Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(), | ||||
|             Type::BooleanLiteral(b) => Some(Some(i32::from(*b))), | ||||
|             Type::NominalInstance(instance) | ||||
|                 if instance.class.is_known(db, KnownClass::NoneType) => | ||||
|             { | ||||
|                 Some(None) | ||||
|             } | ||||
|             _ => None, | ||||
|         }; | ||||
|         Some(SliceLiteral { | ||||
|             start: to_u32(start)?, | ||||
|             stop: to_u32(stop)?, | ||||
|             step: to_u32(step)?, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, PartialEq, Eq, salsa::Update, get_size2::GetSize)] | ||||
| pub(super) struct MetaclassError<'db> { | ||||
|     kind: MetaclassErrorKind<'db>, | ||||
|  | @ -4663,16 +4713,14 @@ impl SlotsKind { | |||
| 
 | ||||
|         match slots_ty { | ||||
|             // __slots__ = ("a", "b")
 | ||||
|             Type::Tuple(tuple) => { | ||||
|                 let tuple = tuple.tuple(db); | ||||
|                 if tuple.is_variadic() { | ||||
|                     Self::Dynamic | ||||
|                 } else if tuple.is_empty() { | ||||
|                     Self::Empty | ||||
|                 } else { | ||||
|                     Self::NotEmpty | ||||
|                 } | ||||
|             } | ||||
|             Type::NominalInstance(nominal) => match nominal | ||||
|                 .tuple_spec(db) | ||||
|                 .and_then(|spec| spec.len().into_fixed_length()) | ||||
|             { | ||||
|                 Some(0) => Self::Empty, | ||||
|                 Some(_) => Self::NotEmpty, | ||||
|                 None => Self::Dynamic, | ||||
|             }, | ||||
| 
 | ||||
|             // __slots__ = "abc"  # Same as `("abc",)`
 | ||||
|             Type::StringLiteral(_) => Self::NotEmpty, | ||||
|  |  | |||
|  | @ -87,7 +87,7 @@ impl<'db> ClassBase<'db> { | |||
|             } | ||||
|             Type::GenericAlias(generic) => Some(Self::Class(ClassType::Generic(generic))), | ||||
|             Type::NominalInstance(instance) | ||||
|                 if instance.class.is_known(db, KnownClass::GenericAlias) => | ||||
|                 if instance.class(db).is_known(db, KnownClass::GenericAlias) => | ||||
|             { | ||||
|                 Self::try_from_type(db, todo_type!("GenericAlias instance")) | ||||
|             } | ||||
|  | @ -153,7 +153,6 @@ impl<'db> ClassBase<'db> { | |||
|             | Type::EnumLiteral(_) | ||||
|             | Type::StringLiteral(_) | ||||
|             | Type::LiteralString | ||||
|             | Type::Tuple(_) | ||||
|             | Type::ModuleLiteral(_) | ||||
|             | Type::TypeVar(_) | ||||
|             | Type::BoundSuper(_) | ||||
|  |  | |||
|  | @ -73,9 +73,17 @@ impl Display for DisplayRepresentation<'_> { | |||
|             Type::Dynamic(dynamic) => dynamic.fmt(f), | ||||
|             Type::Never => f.write_str("Never"), | ||||
|             Type::NominalInstance(instance) => { | ||||
|                 match (instance.class, instance.class.known(self.db)) { | ||||
|                 let class = instance.class(self.db); | ||||
| 
 | ||||
|                 match (class, class.known(self.db)) { | ||||
|                     (_, Some(KnownClass::NoneType)) => f.write_str("None"), | ||||
|                     (_, Some(KnownClass::NoDefaultType)) => f.write_str("NoDefault"), | ||||
|                     (ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias | ||||
|                         .specialization(self.db) | ||||
|                         .tuple(self.db) | ||||
|                         .expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`") | ||||
|                         .display(self.db) | ||||
|                         .fmt(f), | ||||
|                     (ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)), | ||||
|                     (ClassType::Generic(alias), _) => alias.display(self.db).fmt(f), | ||||
|                 } | ||||
|  | @ -207,7 +215,6 @@ impl Display for DisplayRepresentation<'_> { | |||
|                     name = enum_literal.name(self.db), | ||||
|                 ) | ||||
|             } | ||||
|             Type::Tuple(specialization) => specialization.tuple(self.db).display(self.db).fmt(f), | ||||
|             Type::TypeVar(bound_typevar) => { | ||||
|                 f.write_str(bound_typevar.typevar(self.db).name(self.db))?; | ||||
|                 if let Some(binding_context) = bound_typevar.binding_context(self.db).name(self.db) | ||||
|  | @ -395,8 +402,8 @@ pub(crate) struct DisplayGenericAlias<'db> { | |||
| 
 | ||||
| impl Display for DisplayGenericAlias<'_> { | ||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { | ||||
|         if self.origin.is_known(self.db, KnownClass::Tuple) { | ||||
|             self.specialization.tuple(self.db).display(self.db).fmt(f) | ||||
|         if let Some(tuple) = self.specialization.tuple(self.db) { | ||||
|             tuple.display(self.db).fmt(f) | ||||
|         } else { | ||||
|             write!( | ||||
|                 f, | ||||
|  |  | |||
|  | @ -119,54 +119,58 @@ pub(crate) fn enum_metadata<'db>( | |||
|             } | ||||
| 
 | ||||
|             let inferred = place_from_bindings(db, bindings); | ||||
| 
 | ||||
|             let value_ty = match inferred { | ||||
|                 Place::Unbound => { | ||||
|                     return None; | ||||
|                 } | ||||
|                 Place::Type(ty, _) => { | ||||
|                     match ty { | ||||
|                     let special_case = match ty { | ||||
|                         Type::Callable(_) | Type::FunctionLiteral(_) => { | ||||
|                             // Some types are specifically disallowed for enum members.
 | ||||
|                             return None; | ||||
|                         } | ||||
|                         // enum.nonmember
 | ||||
|                         Type::NominalInstance(instance) | ||||
|                             if instance.class.is_known(db, KnownClass::Nonmember) => | ||||
|                         { | ||||
|                             return None; | ||||
|                         } | ||||
|                         // enum.member
 | ||||
|                         Type::NominalInstance(instance) | ||||
|                             if instance.class.is_known(db, KnownClass::Member) => | ||||
|                         { | ||||
|                             ty.member(db, "value") | ||||
|                                 .place | ||||
|                                 .ignore_possibly_unbound() | ||||
|                                 .unwrap_or(Type::unknown()) | ||||
|                         } | ||||
|                         // enum.auto
 | ||||
|                         Type::NominalInstance(instance) | ||||
|                             if instance.class.is_known(db, KnownClass::Auto) => | ||||
|                         { | ||||
|                             auto_counter += 1; | ||||
|                             Type::IntLiteral(auto_counter) | ||||
|                         } | ||||
|                         _ => { | ||||
|                             let dunder_get = ty | ||||
|                                 .member_lookup_with_policy( | ||||
|                                     db, | ||||
|                                     "__get__".into(), | ||||
|                                     MemberLookupPolicy::NO_INSTANCE_FALLBACK, | ||||
|                                 ) | ||||
|                                 .place; | ||||
|                         Type::NominalInstance(instance) => match instance.class(db).known(db) { | ||||
|                             // enum.nonmember
 | ||||
|                             Some(KnownClass::Nonmember) => return None, | ||||
| 
 | ||||
|                             match dunder_get { | ||||
|                                 Place::Unbound | Place::Type(Type::Dynamic(_), _) => ty, | ||||
|                             // enum.member
 | ||||
|                             Some(KnownClass::Member) => Some( | ||||
|                                 ty.member(db, "value") | ||||
|                                     .place | ||||
|                                     .ignore_possibly_unbound() | ||||
|                                     .unwrap_or(Type::unknown()), | ||||
|                             ), | ||||
| 
 | ||||
|                                 Place::Type(_, _) => { | ||||
|                                     // Descriptors are not considered members.
 | ||||
|                                     return None; | ||||
|                                 } | ||||
|                             // enum.auto
 | ||||
|                             Some(KnownClass::Auto) => { | ||||
|                                 auto_counter += 1; | ||||
|                                 Some(Type::IntLiteral(auto_counter)) | ||||
|                             } | ||||
| 
 | ||||
|                             _ => None, | ||||
|                         }, | ||||
| 
 | ||||
|                         _ => None, | ||||
|                     }; | ||||
| 
 | ||||
|                     if let Some(special_case) = special_case { | ||||
|                         special_case | ||||
|                     } else { | ||||
|                         let dunder_get = ty | ||||
|                             .member_lookup_with_policy( | ||||
|                                 db, | ||||
|                                 "__get__".into(), | ||||
|                                 MemberLookupPolicy::NO_INSTANCE_FALLBACK, | ||||
|                             ) | ||||
|                             .place; | ||||
| 
 | ||||
|                         match dunder_get { | ||||
|                             Place::Unbound | Place::Type(Type::Dynamic(_), _) => ty, | ||||
| 
 | ||||
|                             Place::Type(_, _) => { | ||||
|                                 // Descriptors are not considered members.
 | ||||
|                                 return None; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|  | @ -203,7 +207,7 @@ pub(crate) fn enum_metadata<'db>( | |||
|                 Ok(PlaceAndQualifiers { | ||||
|                     place: Place::Type(Type::NominalInstance(instance), _), | ||||
|                     .. | ||||
|                 }) if instance.class.is_known(db, KnownClass::Member) => { | ||||
|                 }) if instance.class(db).is_known(db, KnownClass::Member) => { | ||||
|                     // If the attribute is specifically declared with `enum.member`, it is considered a member
 | ||||
|                 } | ||||
|                 _ => { | ||||
|  |  | |||
|  | @ -945,7 +945,7 @@ fn is_instance_truthiness<'db>( | |||
|     let is_instance = |ty: &Type<'_>| { | ||||
|         if let Type::NominalInstance(instance) = ty { | ||||
|             if instance | ||||
|                 .class | ||||
|                 .class(db) | ||||
|                 .iter_mro(db) | ||||
|                 .filter_map(ClassBase::into_class) | ||||
|                 .any(|c| match c { | ||||
|  | @ -994,8 +994,6 @@ fn is_instance_truthiness<'db>( | |||
|                 .is_some_and(is_instance), | ||||
|         ), | ||||
| 
 | ||||
|         Type::Tuple(..) => always_true_if(class.is_known(db, KnownClass::Tuple)), | ||||
| 
 | ||||
|         Type::FunctionLiteral(..) => { | ||||
|             always_true_if(is_instance(&KnownClass::FunctionType.to_instance(db))) | ||||
|         } | ||||
|  |  | |||
|  | @ -10,13 +10,13 @@ use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind}; | |||
| use crate::types::class::ClassType; | ||||
| use crate::types::class_base::ClassBase; | ||||
| use crate::types::infer::infer_definition_types; | ||||
| use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType}; | ||||
| use crate::types::instance::{Protocol, ProtocolInstanceType}; | ||||
| use crate::types::signatures::{Parameter, Parameters, Signature}; | ||||
| use crate::types::tuple::{TupleSpec, TupleType}; | ||||
| use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; | ||||
| use crate::types::{ | ||||
|     BoundTypeVarInstance, KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, | ||||
|     TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, binding_type, | ||||
|     declaration_type, | ||||
|     BoundTypeVarInstance, KnownClass, KnownInstanceType, Type, TypeMapping, TypeRelation, | ||||
|     TypeTransformer, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarVariance, UnionType, | ||||
|     binding_type, declaration_type, | ||||
| }; | ||||
| use crate::{Db, FxOrderSet}; | ||||
| 
 | ||||
|  | @ -230,8 +230,22 @@ impl<'db> GenericContext<'db> { | |||
|         parameter | ||||
|     } | ||||
| 
 | ||||
|     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 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), | ||||
|                 TupleType::homogeneous(db, Type::unknown()), | ||||
|             ) | ||||
|         } else { | ||||
|             partial | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> { | ||||
|  | @ -401,24 +415,14 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si | |||
|         visitor.visit_type(db, *ty); | ||||
|     } | ||||
|     if let Some(tuple) = specialization.tuple_inner(db) { | ||||
|         visitor.visit_tuple_type(db, tuple); | ||||
|         walk_tuple_type(db, tuple, visitor); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<'db> Specialization<'db> { | ||||
|     /// Returns the tuple spec for a specialization of the `tuple` class.
 | ||||
|     pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> { | ||||
|         if let Some(tuple) = self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db)) { | ||||
|             return tuple; | ||||
|         } | ||||
|         if let [element_type] = self.types(db) { | ||||
|             if let Some(tuple) = TupleType::new(db, TupleSpec::homogeneous(*element_type)) { | ||||
|                 return tuple.tuple(db); | ||||
|             } | ||||
|         } | ||||
|         TupleType::new(db, TupleSpec::homogeneous(Type::unknown())) | ||||
|             .expect("tuple[Unknown, ...] should never contain Never") | ||||
|             .tuple(db) | ||||
|     pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> { | ||||
|         self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db)) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this
 | ||||
|  | @ -628,6 +632,16 @@ 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; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         true | ||||
|     } | ||||
| 
 | ||||
|  | @ -640,6 +654,8 @@ impl<'db> Specialization<'db> { | |||
|         for ty in self.types(db) { | ||||
|             ty.find_legacy_typevars(db, binding_context, typevars); | ||||
|         } | ||||
|         // A tuple's specialization will include all of its element types, so we don't need to also
 | ||||
|         // look in `self.tuple`.
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -834,60 +850,67 @@ impl<'db> SpecializationBuilder<'db> { | |||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             (Type::Tuple(formal_tuple), Type::Tuple(actual_tuple)) => { | ||||
|                 let formal_tuple = formal_tuple.tuple(self.db); | ||||
|                 let actual_tuple = actual_tuple.tuple(self.db); | ||||
|                 let Some(most_precise_length) = formal_tuple.len().most_precise(actual_tuple.len()) else { | ||||
|                     return Ok(()); | ||||
|                 }; | ||||
|                 let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else { | ||||
|                     return Ok(()); | ||||
|                 }; | ||||
|                 let Ok(actual_tuple) = actual_tuple.resize(self.db, most_precise_length) else { | ||||
|                     return Ok(()); | ||||
|                 }; | ||||
|                 for (formal_element, actual_element) in | ||||
|                     formal_tuple.all_elements().zip(actual_tuple.all_elements()) | ||||
|                 { | ||||
|                     self.infer(*formal_element, *actual_element)?; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             ( | ||||
|                 Type::NominalInstance(NominalInstanceType { | ||||
|                     class: ClassType::Generic(formal_alias), | ||||
|                     .. | ||||
|                 }) | ||||
|                 // TODO: This will only handle classes that explicit implement a generic protocol
 | ||||
|                 // by listing it as a base class. To handle classes that implicitly implement a
 | ||||
|                 // generic protocol, we will need to check the types of the protocol members to be
 | ||||
|                 // able to infer the specialization of the protocol that the class implements.
 | ||||
|                 | Type::ProtocolInstance(ProtocolInstanceType { | ||||
|                     inner: Protocol::FromClass(ClassType::Generic(formal_alias)), | ||||
|                     .. | ||||
|                 }), | ||||
|                 Type::NominalInstance(NominalInstanceType { | ||||
|                     class: actual_class, | ||||
|                     .. | ||||
|                 }), | ||||
|             ) => { | ||||
|                 let formal_origin = formal_alias.origin(self.db); | ||||
|                 for base in actual_class.iter_mro(self.db) { | ||||
|                     let ClassBase::Class(ClassType::Generic(base_alias)) = base else { | ||||
|                         continue; | ||||
|             (formal, Type::NominalInstance(actual_nominal)) => { | ||||
|                 // Special case: `formal` and `actual` are both tuples.
 | ||||
|                 if let (Some(formal_tuple), Some(actual_tuple)) = ( | ||||
|                     formal.tuple_instance_spec(self.db), | ||||
|                     actual_nominal.tuple_spec(self.db), | ||||
|                 ) { | ||||
|                     let Some(most_precise_length) = | ||||
|                         formal_tuple.len().most_precise(actual_tuple.len()) | ||||
|                     else { | ||||
|                         return Ok(()); | ||||
|                     }; | ||||
|                     if formal_origin != base_alias.origin(self.db) { | ||||
|                         continue; | ||||
|                     } | ||||
|                     let formal_specialization = formal_alias.specialization(self.db).types(self.db); | ||||
|                     let base_specialization = base_alias.specialization(self.db).types(self.db); | ||||
|                     for (formal_ty, base_ty) in | ||||
|                         formal_specialization.iter().zip(base_specialization) | ||||
|                     let Ok(formal_tuple) = formal_tuple.resize(self.db, most_precise_length) else { | ||||
|                         return Ok(()); | ||||
|                     }; | ||||
|                     let Ok(actual_tuple) = actual_tuple.resize(self.db, most_precise_length) else { | ||||
|                         return Ok(()); | ||||
|                     }; | ||||
|                     for (formal_element, actual_element) in | ||||
|                         formal_tuple.all_elements().zip(actual_tuple.all_elements()) | ||||
|                     { | ||||
|                         self.infer(*formal_ty, *base_ty)?; | ||||
|                         self.infer(*formal_element, *actual_element)?; | ||||
|                     } | ||||
|                     return Ok(()); | ||||
|                 } | ||||
| 
 | ||||
|                 // Extract formal_alias if this is a generic class
 | ||||
|                 let formal_alias = match formal { | ||||
|                     Type::NominalInstance(formal_nominal) => { | ||||
|                         formal_nominal.class(self.db).into_generic_alias() | ||||
|                     } | ||||
|                     // TODO: This will only handle classes that explicit implement a generic protocol
 | ||||
|                     // by listing it as a base class. To handle classes that implicitly implement a
 | ||||
|                     // generic protocol, we will need to check the types of the protocol members to be
 | ||||
|                     // able to infer the specialization of the protocol that the class implements.
 | ||||
|                     Type::ProtocolInstance(ProtocolInstanceType { | ||||
|                         inner: Protocol::FromClass(ClassType::Generic(alias)), | ||||
|                         .. | ||||
|                     }) => Some(alias), | ||||
|                     _ => None, | ||||
|                 }; | ||||
| 
 | ||||
|                 if let Some(formal_alias) = formal_alias { | ||||
|                     let formal_origin = formal_alias.origin(self.db); | ||||
|                     for base in actual_nominal.class(self.db).iter_mro(self.db) { | ||||
|                         let ClassBase::Class(ClassType::Generic(base_alias)) = base else { | ||||
|                             continue; | ||||
|                         }; | ||||
|                         if formal_origin != base_alias.origin(self.db) { | ||||
|                             continue; | ||||
|                         } | ||||
|                         let formal_specialization = | ||||
|                             formal_alias.specialization(self.db).types(self.db); | ||||
|                         let base_specialization = base_alias.specialization(self.db).types(self.db); | ||||
|                         for (formal_ty, base_ty) in | ||||
|                             formal_specialization.iter().zip(base_specialization) | ||||
|                         { | ||||
|                             self.infer(*formal_ty, *base_ty)?; | ||||
|                         } | ||||
|                         return Ok(()); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             // TODO: Add more forms that we can structurally induct into: type[C], callables
 | ||||
|  |  | |||
|  | @ -94,7 +94,7 @@ impl<'db> AllMembers<'db> { | |||
|             ), | ||||
| 
 | ||||
|             Type::NominalInstance(instance) => { | ||||
|                 let (class_literal, _specialization) = instance.class.class_literal(db); | ||||
|                 let (class_literal, _specialization) = instance.class(db).class_literal(db); | ||||
|                 self.extend_with_instance_members(db, ty, class_literal); | ||||
|             } | ||||
| 
 | ||||
|  | @ -137,7 +137,6 @@ impl<'db> AllMembers<'db> { | |||
|             | Type::BytesLiteral(_) | ||||
|             | Type::EnumLiteral(_) | ||||
|             | Type::LiteralString | ||||
|             | Type::Tuple(_) | ||||
|             | Type::PropertyInstance(_) | ||||
|             | Type::FunctionLiteral(_) | ||||
|             | Type::BoundMethod(_) | ||||
|  | @ -208,7 +207,7 @@ impl<'db> AllMembers<'db> { | |||
|                         match ty { | ||||
|                             Type::NominalInstance(instance) | ||||
|                                 if matches!( | ||||
|                                     instance.class.known(db), | ||||
|                                     instance.class(db).known(db), | ||||
|                                     Some( | ||||
|                                         KnownClass::TypeVar | ||||
|                                             | KnownClass::TypeVarTuple | ||||
|  | @ -868,7 +867,7 @@ mod resolve_definition { | |||
|     pub enum ImportAliasResolution { | ||||
|         /// Resolve import aliases to their original definitions
 | ||||
|         ResolveAliases, | ||||
|         /// Keep import aliases as-is, don't resolve to original definitions  
 | ||||
|         /// Keep import aliases as-is, don't resolve to original definitions
 | ||||
|         PreserveAliases, | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -62,7 +62,7 @@ use super::string_annotation::{ | |||
|     BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, | ||||
| }; | ||||
| use super::subclass_of::SubclassOfInner; | ||||
| use super::{ClassBase, NominalInstanceType, add_inferred_python_version_hint_to_diagnostic}; | ||||
| use super::{ClassBase, add_inferred_python_version_hint_to_diagnostic}; | ||||
| use crate::module_name::{ModuleName, ModuleNameResolutionError}; | ||||
| use crate::module_resolver::resolve_module; | ||||
| use crate::node_key::NodeKey; | ||||
|  | @ -90,7 +90,7 @@ use crate::semantic_index::{ | |||
|     ApplicableConstraints, EnclosingSnapshotResult, SemanticIndex, place_table, semantic_index, | ||||
| }; | ||||
| use crate::types::call::{Binding, Bindings, CallArguments, CallError, CallErrorKind}; | ||||
| use crate::types::class::{CodeGeneratorKind, Field, MetaclassErrorKind, SliceLiteral}; | ||||
| use crate::types::class::{CodeGeneratorKind, Field, MetaclassErrorKind}; | ||||
| use crate::types::diagnostic::{ | ||||
|     self, CALL_NON_CALLABLE, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS, | ||||
|     CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_KW_ONLY, INCONSISTENT_MRO, | ||||
|  | @ -111,9 +111,10 @@ use crate::types::function::{ | |||
|     FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, | ||||
| }; | ||||
| use crate::types::generics::{GenericContext, bind_typevar}; | ||||
| use crate::types::instance::SliceLiteral; | ||||
| use crate::types::mro::MroErrorKind; | ||||
| use crate::types::signatures::{CallableSignature, Signature}; | ||||
| use crate::types::tuple::{TupleSpec, TupleType}; | ||||
| use crate::types::tuple::{Tuple, TupleSpec, TupleType}; | ||||
| use crate::types::unpacker::{UnpackResult, Unpacker}; | ||||
| use crate::types::{ | ||||
|     CallDunderError, CallableType, ClassLiteral, ClassType, DataclassParams, DynamicType, | ||||
|  | @ -1375,7 +1376,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                         continue; | ||||
|                     }; | ||||
| 
 | ||||
|                     if !instance.class.is_known(self.db(), KnownClass::KwOnly) { | ||||
|                     if !instance | ||||
|                         .class(self.db()) | ||||
|                         .is_known(self.db(), KnownClass::KwOnly) | ||||
|                     { | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|  | @ -1772,7 +1776,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             Type::BooleanLiteral(_) | Type::IntLiteral(_) => {} | ||||
|             Type::NominalInstance(instance) | ||||
|                 if matches!( | ||||
|                     instance.class.known(self.db()), | ||||
|                     instance.class(self.db()).known(self.db()), | ||||
|                     Some(KnownClass::Float | KnownClass::Int | KnownClass::Bool) | ||||
|                 ) => {} | ||||
|             _ => return false, | ||||
|  | @ -3286,22 +3290,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|     } | ||||
| 
 | ||||
|     fn infer_exception(&mut self, node: Option<&ast::Expr>, is_star: bool) -> Type<'db> { | ||||
|         fn extract_tuple_specialization<'db>(db: &'db dyn Db, ty: Type<'db>) -> Option<Type<'db>> { | ||||
|             let class = ty.into_nominal_instance()?.class; | ||||
|             if !class.is_known(db, KnownClass::Tuple) { | ||||
|                 return None; | ||||
|             } | ||||
|             let ClassType::Generic(class) = class else { | ||||
|                 return None; | ||||
|             }; | ||||
|             let specialization = class.specialization(db).types(db)[0]; | ||||
|             let specialization_instance = specialization.to_instance(db)?; | ||||
| 
 | ||||
|             specialization_instance | ||||
|                 .is_assignable_to(db, KnownClass::BaseException.to_instance(db)) | ||||
|                 .then_some(specialization_instance) | ||||
|         } | ||||
| 
 | ||||
|         // If there is no handled exception, it's invalid syntax;
 | ||||
|         // a diagnostic will have already been emitted
 | ||||
|         let node_ty = node.map_or(Type::unknown(), |ty| self.infer_expression(ty)); | ||||
|  | @ -3309,9 +3297,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
| 
 | ||||
|         // If it's an `except*` handler, this won't actually be the type of the bound symbol;
 | ||||
|         // it will actually be the type of the generic parameters to `BaseExceptionGroup` or `ExceptionGroup`.
 | ||||
|         let symbol_ty = if let Type::Tuple(tuple) = node_ty { | ||||
|         let symbol_ty = if let Some(tuple_spec) = node_ty.tuple_instance_spec(self.db()) { | ||||
|             let mut builder = UnionBuilder::new(self.db()); | ||||
|             for element in tuple.tuple(self.db()).all_elements().copied() { | ||||
|             for element in tuple_spec.all_elements().copied() { | ||||
|                 builder = builder.add( | ||||
|                     if element.is_assignable_to(self.db(), type_base_exception) { | ||||
|                         element.to_instance(self.db()).expect( | ||||
|  | @ -3336,7 +3324,22 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             self.db(), | ||||
|             Type::homogeneous_tuple(self.db(), type_base_exception), | ||||
|         ) { | ||||
|             extract_tuple_specialization(self.db(), node_ty) | ||||
|             node_ty | ||||
|                 .tuple_instance_spec(self.db()) | ||||
|                 .and_then(|spec| { | ||||
|                     let specialization = spec | ||||
|                         .homogeneous_element_type(self.db()) | ||||
|                         .to_instance(self.db()); | ||||
| 
 | ||||
|                     debug_assert!(specialization.is_some_and(|specialization_type| { | ||||
|                         specialization_type.is_assignable_to( | ||||
|                             self.db(), | ||||
|                             KnownClass::BaseException.to_instance(self.db()), | ||||
|                         ) | ||||
|                     })); | ||||
| 
 | ||||
|                     specialization | ||||
|                 }) | ||||
|                 .unwrap_or_else(|| KnownClass::BaseException.to_instance(self.db())) | ||||
|         } else if node_ty.is_assignable_to( | ||||
|             self.db(), | ||||
|  | @ -3954,7 +3957,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             } | ||||
| 
 | ||||
|             // Super instances do not allow attribute assignment
 | ||||
|             Type::NominalInstance(instance) if instance.class.is_known(db, KnownClass::Super) => { | ||||
|             Type::NominalInstance(instance) | ||||
|                 if instance.class(db).is_known(db, KnownClass::Super) => | ||||
|             { | ||||
|                 if emit_diagnostics { | ||||
|                     if let Some(builder) = self.context.report_lint(&INVALID_ASSIGNMENT, target) { | ||||
|                         builder.into_diagnostic(format_args!( | ||||
|  | @ -3987,7 +3992,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             | Type::BytesLiteral(..) | ||||
|             | Type::EnumLiteral(..) | ||||
|             | Type::LiteralString | ||||
|             | Type::Tuple(..) | ||||
|             | Type::SpecialForm(..) | ||||
|             | Type::KnownInstance(..) | ||||
|             | Type::PropertyInstance(..) | ||||
|  | @ -4403,15 +4407,17 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             ast::Expr::Name(name) => self.infer_definition(name), | ||||
|             ast::Expr::List(ast::ExprList { elts, .. }) | ||||
|             | ast::Expr::Tuple(ast::ExprTuple { elts, .. }) => { | ||||
|                 let mut assigned_tys = match assigned_ty { | ||||
|                     Some(Type::Tuple(tuple)) => { | ||||
|                         Either::Left(tuple.tuple(self.db()).all_elements().copied()) | ||||
|                 if let Some(tuple_spec) = | ||||
|                     assigned_ty.and_then(|ty| ty.tuple_instance_spec(self.db())) | ||||
|                 { | ||||
|                     let mut assigned_tys = tuple_spec.all_elements(); | ||||
|                     for element in elts { | ||||
|                         self.infer_target_impl(element, value, assigned_tys.next().copied()); | ||||
|                     } | ||||
|                 } else { | ||||
|                     for element in elts { | ||||
|                         self.infer_target_impl(element, value, None); | ||||
|                     } | ||||
|                     Some(_) | None => Either::Right(std::iter::empty()), | ||||
|                 }; | ||||
| 
 | ||||
|                 for element in elts { | ||||
|                     self.infer_target_impl(element, value, assigned_tys.next()); | ||||
|                 } | ||||
|             } | ||||
|             ast::Expr::Attribute( | ||||
|  | @ -4613,7 +4619,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
| 
 | ||||
|         // Handle various singletons.
 | ||||
|         if let Type::NominalInstance(instance) = declared.inner_type() { | ||||
|             if instance.class.is_known(self.db(), KnownClass::SpecialForm) { | ||||
|             if instance | ||||
|                 .class(self.db()) | ||||
|                 .is_known(self.db(), KnownClass::SpecialForm) | ||||
|             { | ||||
|                 if let Some(name_expr) = target.as_name_expr() { | ||||
|                     if let Some(special_form) = SpecialFormType::try_from_file_and_name( | ||||
|                         self.db(), | ||||
|  | @ -7182,7 +7191,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                 | Type::LiteralString | ||||
|                 | Type::BytesLiteral(_) | ||||
|                 | Type::EnumLiteral(_) | ||||
|                 | Type::Tuple(_) | ||||
|                 | Type::BoundSuper(_) | ||||
|                 | Type::TypeVar(_) | ||||
|                 | Type::TypeIs(_) | ||||
|  | @ -7508,7 +7516,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                 | Type::LiteralString | ||||
|                 | Type::BytesLiteral(_) | ||||
|                 | Type::EnumLiteral(_) | ||||
|                 | Type::Tuple(_) | ||||
|                 | Type::BoundSuper(_) | ||||
|                 | Type::TypeVar(_) | ||||
|                 | Type::TypeIs(_) | ||||
|  | @ -7538,7 +7545,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                 | Type::LiteralString | ||||
|                 | Type::BytesLiteral(_) | ||||
|                 | Type::EnumLiteral(_) | ||||
|                 | Type::Tuple(_) | ||||
|                 | Type::BoundSuper(_) | ||||
|                 | Type::TypeVar(_) | ||||
|                 | Type::TypeIs(_) | ||||
|  | @ -7938,14 +7944,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|         // language spec.
 | ||||
|         // - `[ast::CompOp::Is]`: return `false` if unequal, `bool` if equal
 | ||||
|         // - `[ast::CompOp::IsNot]`: return `true` if unequal, `bool` if equal
 | ||||
|         match (left, right) { | ||||
|         let comparison_result = match (left, right) { | ||||
|             (Type::Union(union), other) => { | ||||
|                 let mut builder = UnionBuilder::new(self.db()); | ||||
|                 for element in union.elements(self.db()) { | ||||
|                     builder = | ||||
|                         builder.add(self.infer_binary_type_comparison(*element, op, other, range)?); | ||||
|                 } | ||||
|                 Ok(builder.build()) | ||||
|                 Some(Ok(builder.build())) | ||||
|             } | ||||
|             (other, Type::Union(union)) => { | ||||
|                 let mut builder = UnionBuilder::new(self.db()); | ||||
|  | @ -7953,27 +7959,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                     builder = | ||||
|                         builder.add(self.infer_binary_type_comparison(other, op, *element, range)?); | ||||
|                 } | ||||
|                 Ok(builder.build()) | ||||
|                 Some(Ok(builder.build())) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Intersection(intersection), right) => self | ||||
|                 .infer_binary_intersection_type_comparison( | ||||
|             (Type::Intersection(intersection), right) => { | ||||
|                 Some(self.infer_binary_intersection_type_comparison( | ||||
|                     intersection, | ||||
|                     op, | ||||
|                     right, | ||||
|                     IntersectionOn::Left, | ||||
|                     range, | ||||
|                 ), | ||||
|             (left, Type::Intersection(intersection)) => self | ||||
|                 .infer_binary_intersection_type_comparison( | ||||
|                 )) | ||||
|             } | ||||
|             (left, Type::Intersection(intersection)) => { | ||||
|                 Some(self.infer_binary_intersection_type_comparison( | ||||
|                     intersection, | ||||
|                     op, | ||||
|                     left, | ||||
|                     IntersectionOn::Right, | ||||
|                     range, | ||||
|                 ), | ||||
|                 )) | ||||
|             } | ||||
| 
 | ||||
|             (Type::IntLiteral(n), Type::IntLiteral(m)) => match op { | ||||
|             (Type::IntLiteral(n), Type::IntLiteral(m)) => Some(match op { | ||||
|                 ast::CmpOp::Eq => Ok(Type::BooleanLiteral(n == m)), | ||||
|                 ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(n != m)), | ||||
|                 ast::CmpOp::Lt => Ok(Type::BooleanLiteral(n < m)), | ||||
|  | @ -8002,45 +8010,54 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                     left_ty: left, | ||||
|                     right_ty: right, | ||||
|                 }), | ||||
|             }, | ||||
|             (Type::IntLiteral(_), Type::NominalInstance(_)) => self.infer_binary_type_comparison( | ||||
|                 KnownClass::Int.to_instance(self.db()), | ||||
|                 op, | ||||
|                 right, | ||||
|                 range, | ||||
|             ), | ||||
|             (Type::NominalInstance(_), Type::IntLiteral(_)) => self.infer_binary_type_comparison( | ||||
|                 left, | ||||
|                 op, | ||||
|                 KnownClass::Int.to_instance(self.db()), | ||||
|                 range, | ||||
|             ), | ||||
|             }), | ||||
|             (Type::IntLiteral(_), Type::NominalInstance(_)) => { | ||||
|                 Some(self.infer_binary_type_comparison( | ||||
|                     KnownClass::Int.to_instance(self.db()), | ||||
|                     op, | ||||
|                     right, | ||||
|                     range, | ||||
|                 )) | ||||
|             } | ||||
|             (Type::NominalInstance(_), Type::IntLiteral(_)) => { | ||||
|                 Some(self.infer_binary_type_comparison( | ||||
|                     left, | ||||
|                     op, | ||||
|                     KnownClass::Int.to_instance(self.db()), | ||||
|                     range, | ||||
|                 )) | ||||
|             } | ||||
| 
 | ||||
|             // Booleans are coded as integers (False = 0, True = 1)
 | ||||
|             (Type::IntLiteral(n), Type::BooleanLiteral(b)) => self.infer_binary_type_comparison( | ||||
|                 Type::IntLiteral(n), | ||||
|                 op, | ||||
|                 Type::IntLiteral(i64::from(b)), | ||||
|                 range, | ||||
|             ), | ||||
|             (Type::BooleanLiteral(b), Type::IntLiteral(m)) => self.infer_binary_type_comparison( | ||||
|                 Type::IntLiteral(i64::from(b)), | ||||
|                 op, | ||||
|                 Type::IntLiteral(m), | ||||
|                 range, | ||||
|             ), | ||||
|             (Type::BooleanLiteral(a), Type::BooleanLiteral(b)) => self | ||||
|                 .infer_binary_type_comparison( | ||||
|             (Type::IntLiteral(n), Type::BooleanLiteral(b)) => { | ||||
|                 Some(self.infer_binary_type_comparison( | ||||
|                     Type::IntLiteral(n), | ||||
|                     op, | ||||
|                     Type::IntLiteral(i64::from(b)), | ||||
|                     range, | ||||
|                 )) | ||||
|             } | ||||
|             (Type::BooleanLiteral(b), Type::IntLiteral(m)) => { | ||||
|                 Some(self.infer_binary_type_comparison( | ||||
|                     Type::IntLiteral(i64::from(b)), | ||||
|                     op, | ||||
|                     Type::IntLiteral(m), | ||||
|                     range, | ||||
|                 )) | ||||
|             } | ||||
|             (Type::BooleanLiteral(a), Type::BooleanLiteral(b)) => { | ||||
|                 Some(self.infer_binary_type_comparison( | ||||
|                     Type::IntLiteral(i64::from(a)), | ||||
|                     op, | ||||
|                     Type::IntLiteral(i64::from(b)), | ||||
|                     range, | ||||
|                 ), | ||||
|                 )) | ||||
|             } | ||||
| 
 | ||||
|             (Type::StringLiteral(salsa_s1), Type::StringLiteral(salsa_s2)) => { | ||||
|                 let s1 = salsa_s1.value(self.db()); | ||||
|                 let s2 = salsa_s2.value(self.db()); | ||||
|                 match op { | ||||
|                 let result = match op { | ||||
|                     ast::CmpOp::Eq => Ok(Type::BooleanLiteral(s1 == s2)), | ||||
|                     ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(s1 != s2)), | ||||
|                     ast::CmpOp::Lt => Ok(Type::BooleanLiteral(s1 < s2)), | ||||
|  | @ -8063,38 +8080,39 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                             Ok(Type::BooleanLiteral(true)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 }; | ||||
|                 Some(result) | ||||
|             } | ||||
|             (Type::StringLiteral(_), _) => self.infer_binary_type_comparison( | ||||
|             (Type::StringLiteral(_), _) => Some(self.infer_binary_type_comparison( | ||||
|                 KnownClass::Str.to_instance(self.db()), | ||||
|                 op, | ||||
|                 right, | ||||
|                 range, | ||||
|             ), | ||||
|             (_, Type::StringLiteral(_)) => self.infer_binary_type_comparison( | ||||
|             )), | ||||
|             (_, Type::StringLiteral(_)) => Some(self.infer_binary_type_comparison( | ||||
|                 left, | ||||
|                 op, | ||||
|                 KnownClass::Str.to_instance(self.db()), | ||||
|                 range, | ||||
|             ), | ||||
|             )), | ||||
| 
 | ||||
|             (Type::LiteralString, _) => self.infer_binary_type_comparison( | ||||
|             (Type::LiteralString, _) => Some(self.infer_binary_type_comparison( | ||||
|                 KnownClass::Str.to_instance(self.db()), | ||||
|                 op, | ||||
|                 right, | ||||
|                 range, | ||||
|             ), | ||||
|             (_, Type::LiteralString) => self.infer_binary_type_comparison( | ||||
|             )), | ||||
|             (_, Type::LiteralString) => Some(self.infer_binary_type_comparison( | ||||
|                 left, | ||||
|                 op, | ||||
|                 KnownClass::Str.to_instance(self.db()), | ||||
|                 range, | ||||
|             ), | ||||
|             )), | ||||
| 
 | ||||
|             (Type::BytesLiteral(salsa_b1), Type::BytesLiteral(salsa_b2)) => { | ||||
|                 let b1 = salsa_b1.value(self.db()); | ||||
|                 let b2 = salsa_b2.value(self.db()); | ||||
|                 match op { | ||||
|                 let result = match op { | ||||
|                     ast::CmpOp::Eq => Ok(Type::BooleanLiteral(b1 == b2)), | ||||
|                     ast::CmpOp::NotEq => Ok(Type::BooleanLiteral(b1 != b2)), | ||||
|                     ast::CmpOp::Lt => Ok(Type::BooleanLiteral(b1 < b2)), | ||||
|  | @ -8121,161 +8139,142 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                             Ok(Type::BooleanLiteral(true)) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 }; | ||||
|                 Some(result) | ||||
|             } | ||||
|             (Type::BytesLiteral(_), _) => self.infer_binary_type_comparison( | ||||
|             (Type::BytesLiteral(_), _) => Some(self.infer_binary_type_comparison( | ||||
|                 KnownClass::Bytes.to_instance(self.db()), | ||||
|                 op, | ||||
|                 right, | ||||
|                 range, | ||||
|             ), | ||||
|             (_, Type::BytesLiteral(_)) => self.infer_binary_type_comparison( | ||||
|             )), | ||||
|             (_, Type::BytesLiteral(_)) => Some(self.infer_binary_type_comparison( | ||||
|                 left, | ||||
|                 op, | ||||
|                 KnownClass::Bytes.to_instance(self.db()), | ||||
|                 range, | ||||
|             ), | ||||
|             )), | ||||
| 
 | ||||
|             (Type::EnumLiteral(literal_1), Type::EnumLiteral(literal_2)) | ||||
|                 if op == ast::CmpOp::Eq => | ||||
|             { | ||||
|                 Ok(Type::BooleanLiteral(literal_1 == literal_2)) | ||||
|                 Some(Ok(Type::BooleanLiteral(literal_1 == literal_2))) | ||||
|             } | ||||
|             (Type::EnumLiteral(literal_1), Type::EnumLiteral(literal_2)) | ||||
|                 if op == ast::CmpOp::NotEq => | ||||
|             { | ||||
|                 Ok(Type::BooleanLiteral(literal_1 != literal_2)) | ||||
|                 Some(Ok(Type::BooleanLiteral(literal_1 != literal_2))) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Tuple(_), Type::NominalInstance(instance)) | ||||
|                 if instance.class.is_known(self.db(), KnownClass::VersionInfo) => | ||||
|             { | ||||
|                 self.infer_binary_type_comparison( | ||||
|                     left, | ||||
|                     op, | ||||
|                     Type::version_info_tuple(self.db()), | ||||
|                     range, | ||||
|                 ) | ||||
|             } | ||||
|             (Type::NominalInstance(instance), Type::Tuple(_)) | ||||
|                 if instance.class.is_known(self.db(), KnownClass::VersionInfo) => | ||||
|             { | ||||
|                 self.infer_binary_type_comparison( | ||||
|                     Type::version_info_tuple(self.db()), | ||||
|                     op, | ||||
|                     right, | ||||
|                     range, | ||||
|                 ) | ||||
|             } | ||||
|             (Type::Tuple(lhs), Type::Tuple(rhs)) => { | ||||
|                 let lhs_tuple = lhs.tuple(self.db()); | ||||
|                 let rhs_tuple = rhs.tuple(self.db()); | ||||
|             ( | ||||
|                 Type::NominalInstance(nominal1), | ||||
|                 Type::NominalInstance(nominal2), | ||||
|             ) => nominal1.tuple_spec(self.db()) | ||||
|                 .and_then(|lhs_tuple| Some((lhs_tuple, nominal2.tuple_spec(self.db())?))) | ||||
|                 .map(|(lhs_tuple, rhs_tuple)| { | ||||
|                     let mut tuple_rich_comparison = | ||||
|                         |op| self.infer_tuple_rich_comparison(&lhs_tuple, op, &rhs_tuple, range); | ||||
| 
 | ||||
|                 let mut tuple_rich_comparison = | ||||
|                     |op| self.infer_tuple_rich_comparison(lhs_tuple, op, rhs_tuple, range); | ||||
|                     match op { | ||||
|                         ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq), | ||||
|                         ast::CmpOp::NotEq => tuple_rich_comparison(RichCompareOperator::Ne), | ||||
|                         ast::CmpOp::Lt => tuple_rich_comparison(RichCompareOperator::Lt), | ||||
|                         ast::CmpOp::LtE => tuple_rich_comparison(RichCompareOperator::Le), | ||||
|                         ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt), | ||||
|                         ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge), | ||||
|                         ast::CmpOp::In | ast::CmpOp::NotIn => { | ||||
|                             let mut any_eq = false; | ||||
|                             let mut any_ambiguous = false; | ||||
| 
 | ||||
|                 match op { | ||||
|                     ast::CmpOp::Eq => tuple_rich_comparison(RichCompareOperator::Eq), | ||||
|                     ast::CmpOp::NotEq => tuple_rich_comparison(RichCompareOperator::Ne), | ||||
|                     ast::CmpOp::Lt => tuple_rich_comparison(RichCompareOperator::Lt), | ||||
|                     ast::CmpOp::LtE => tuple_rich_comparison(RichCompareOperator::Le), | ||||
|                     ast::CmpOp::Gt => tuple_rich_comparison(RichCompareOperator::Gt), | ||||
|                     ast::CmpOp::GtE => tuple_rich_comparison(RichCompareOperator::Ge), | ||||
|                     ast::CmpOp::In | ast::CmpOp::NotIn => { | ||||
|                         let mut any_eq = false; | ||||
|                         let mut any_ambiguous = false; | ||||
| 
 | ||||
|                         for ty in rhs_tuple.all_elements().copied() { | ||||
|                             let eq_result = self.infer_binary_type_comparison( | ||||
|                                 Type::Tuple(lhs), | ||||
|                             for ty in rhs_tuple.all_elements().copied() { | ||||
|                                 let eq_result = self.infer_binary_type_comparison( | ||||
|                                 left, | ||||
|                                 ast::CmpOp::Eq, | ||||
|                                 ty, | ||||
|                                 range, | ||||
|                             ).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`"); | ||||
| 
 | ||||
|                             match eq_result { | ||||
|                                 todo @ Type::Dynamic(DynamicType::Todo(_)) => return Ok(todo), | ||||
|                                 // It's okay to ignore errors here because Python doesn't call `__bool__`
 | ||||
|                                 // for different union variants. Instead, this is just for us to
 | ||||
|                                 // evaluate a possibly truthy value to `false` or `true`.
 | ||||
|                                 ty => match ty.bool(self.db()) { | ||||
|                                     Truthiness::AlwaysTrue => any_eq = true, | ||||
|                                     Truthiness::AlwaysFalse => (), | ||||
|                                     Truthiness::Ambiguous => any_ambiguous = true, | ||||
|                                 }, | ||||
|                                 match eq_result { | ||||
|                                     todo @ Type::Dynamic(DynamicType::Todo(_)) => return Ok(todo), | ||||
|                                     // It's okay to ignore errors here because Python doesn't call `__bool__`
 | ||||
|                                     // for different union variants. Instead, this is just for us to
 | ||||
|                                     // evaluate a possibly truthy value to `false` or `true`.
 | ||||
|                                     ty => match ty.bool(self.db()) { | ||||
|                                         Truthiness::AlwaysTrue => any_eq = true, | ||||
|                                         Truthiness::AlwaysFalse => (), | ||||
|                                         Truthiness::Ambiguous => any_ambiguous = true, | ||||
|                                     }, | ||||
|                                 } | ||||
|                             } | ||||
| 
 | ||||
|                             if any_eq { | ||||
|                                 Ok(Type::BooleanLiteral(op.is_in())) | ||||
|                             } else if !any_ambiguous { | ||||
|                                 Ok(Type::BooleanLiteral(op.is_not_in())) | ||||
|                             } else { | ||||
|                                 Ok(KnownClass::Bool.to_instance(self.db())) | ||||
|                             } | ||||
|                         } | ||||
| 
 | ||||
|                         if any_eq { | ||||
|                             Ok(Type::BooleanLiteral(op.is_in())) | ||||
|                         } else if !any_ambiguous { | ||||
|                             Ok(Type::BooleanLiteral(op.is_not_in())) | ||||
|                         } else { | ||||
|                             Ok(KnownClass::Bool.to_instance(self.db())) | ||||
|                         } | ||||
|                     } | ||||
|                     ast::CmpOp::Is | ast::CmpOp::IsNot => { | ||||
|                         // - `[ast::CmpOp::Is]`: returns `false` if the elements are definitely unequal, otherwise `bool`
 | ||||
|                         // - `[ast::CmpOp::IsNot]`: returns `true` if the elements are definitely unequal, otherwise `bool`
 | ||||
|                         let eq_result = tuple_rich_comparison(RichCompareOperator::Eq).expect( | ||||
|                         ast::CmpOp::Is | ast::CmpOp::IsNot => { | ||||
|                             // - `[ast::CmpOp::Is]`: returns `false` if the elements are definitely unequal, otherwise `bool`
 | ||||
|                             // - `[ast::CmpOp::IsNot]`: returns `true` if the elements are definitely unequal, otherwise `bool`
 | ||||
|                             let eq_result = tuple_rich_comparison(RichCompareOperator::Eq).expect( | ||||
|                             "infer_binary_type_comparison should never return None for `CmpOp::Eq`", | ||||
|                         ); | ||||
| 
 | ||||
|                         Ok(match eq_result { | ||||
|                             todo @ Type::Dynamic(DynamicType::Todo(_)) => todo, | ||||
|                             // It's okay to ignore errors here because Python doesn't call `__bool__`
 | ||||
|                             // for `is` and `is not` comparisons. This is an implementation detail
 | ||||
|                             // for how we determine the truthiness of a type.
 | ||||
|                             ty => match ty.bool(self.db()) { | ||||
|                                 Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()), | ||||
|                                 _ => KnownClass::Bool.to_instance(self.db()), | ||||
|                             }, | ||||
|                         }) | ||||
|                             Ok(match eq_result { | ||||
|                                 todo @ Type::Dynamic(DynamicType::Todo(_)) => todo, | ||||
|                                 // It's okay to ignore errors here because Python doesn't call `__bool__`
 | ||||
|                                 // for `is` and `is not` comparisons. This is an implementation detail
 | ||||
|                                 // for how we determine the truthiness of a type.
 | ||||
|                                 ty => match ty.bool(self.db()) { | ||||
|                                     Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()), | ||||
|                                     _ => KnownClass::Bool.to_instance(self.db()), | ||||
|                                 }, | ||||
|                             }) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             ), | ||||
| 
 | ||||
|             // Lookup the rich comparison `__dunder__` methods
 | ||||
|             _ => { | ||||
|                 let rich_comparison = |op| self.infer_rich_comparison(left, right, op); | ||||
|                 let membership_test_comparison = |op, range: TextRange| { | ||||
|                     self.infer_membership_test_comparison(left, right, op, range) | ||||
|                 }; | ||||
|                 match op { | ||||
|                     ast::CmpOp::Eq => rich_comparison(RichCompareOperator::Eq), | ||||
|                     ast::CmpOp::NotEq => rich_comparison(RichCompareOperator::Ne), | ||||
|                     ast::CmpOp::Lt => rich_comparison(RichCompareOperator::Lt), | ||||
|                     ast::CmpOp::LtE => rich_comparison(RichCompareOperator::Le), | ||||
|                     ast::CmpOp::Gt => rich_comparison(RichCompareOperator::Gt), | ||||
|                     ast::CmpOp::GtE => rich_comparison(RichCompareOperator::Ge), | ||||
|                     ast::CmpOp::In => { | ||||
|                         membership_test_comparison(MembershipTestCompareOperator::In, range) | ||||
|                     } | ||||
|                     ast::CmpOp::NotIn => { | ||||
|                         membership_test_comparison(MembershipTestCompareOperator::NotIn, range) | ||||
|                     } | ||||
|                     ast::CmpOp::Is => { | ||||
|                         if left.is_disjoint_from(self.db(), right) { | ||||
|                             Ok(Type::BooleanLiteral(false)) | ||||
|                         } else if left.is_singleton(self.db()) | ||||
|                             && left.is_equivalent_to(self.db(), right) | ||||
|                         { | ||||
|                             Ok(Type::BooleanLiteral(true)) | ||||
|                         } else { | ||||
|                             Ok(KnownClass::Bool.to_instance(self.db())) | ||||
|                         } | ||||
|                     } | ||||
|                     ast::CmpOp::IsNot => { | ||||
|                         if left.is_disjoint_from(self.db(), right) { | ||||
|                             Ok(Type::BooleanLiteral(true)) | ||||
|                         } else if left.is_singleton(self.db()) | ||||
|                             && left.is_equivalent_to(self.db(), right) | ||||
|                         { | ||||
|                             Ok(Type::BooleanLiteral(false)) | ||||
|                         } else { | ||||
|                             Ok(KnownClass::Bool.to_instance(self.db())) | ||||
|                         } | ||||
|                     } | ||||
|             _ => None, | ||||
|         }; | ||||
| 
 | ||||
|         if let Some(result) = comparison_result { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         // Final generalized fallback: lookup the rich comparison `__dunder__` methods
 | ||||
|         let rich_comparison = |op| self.infer_rich_comparison(left, right, op); | ||||
|         let membership_test_comparison = | ||||
|             |op, range: TextRange| self.infer_membership_test_comparison(left, right, op, range); | ||||
|         match op { | ||||
|             ast::CmpOp::Eq => rich_comparison(RichCompareOperator::Eq), | ||||
|             ast::CmpOp::NotEq => rich_comparison(RichCompareOperator::Ne), | ||||
|             ast::CmpOp::Lt => rich_comparison(RichCompareOperator::Lt), | ||||
|             ast::CmpOp::LtE => rich_comparison(RichCompareOperator::Le), | ||||
|             ast::CmpOp::Gt => rich_comparison(RichCompareOperator::Gt), | ||||
|             ast::CmpOp::GtE => rich_comparison(RichCompareOperator::Ge), | ||||
|             ast::CmpOp::In => membership_test_comparison(MembershipTestCompareOperator::In, range), | ||||
|             ast::CmpOp::NotIn => { | ||||
|                 membership_test_comparison(MembershipTestCompareOperator::NotIn, range) | ||||
|             } | ||||
|             ast::CmpOp::Is => { | ||||
|                 if left.is_disjoint_from(self.db(), right) { | ||||
|                     Ok(Type::BooleanLiteral(false)) | ||||
|                 } else if left.is_singleton(self.db()) && left.is_equivalent_to(self.db(), right) { | ||||
|                     Ok(Type::BooleanLiteral(true)) | ||||
|                 } else { | ||||
|                     Ok(KnownClass::Bool.to_instance(self.db())) | ||||
|                 } | ||||
|             } | ||||
|             ast::CmpOp::IsNot => { | ||||
|                 if left.is_disjoint_from(self.db(), right) { | ||||
|                     Ok(Type::BooleanLiteral(true)) | ||||
|                 } else if left.is_singleton(self.db()) && left.is_equivalent_to(self.db(), right) { | ||||
|                     Ok(Type::BooleanLiteral(false)) | ||||
|                 } else { | ||||
|                     Ok(KnownClass::Bool.to_instance(self.db())) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -8527,10 +8526,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|         let tuple_generic_alias = |db: &'db dyn Db, tuple: Option<TupleType<'db>>| { | ||||
|             let tuple = | ||||
|                 tuple.unwrap_or_else(|| TupleType::homogeneous(db, Type::unknown()).unwrap()); | ||||
|             tuple | ||||
|                 .to_class_type(db) | ||||
|                 .map(Type::from) | ||||
|                 .unwrap_or_else(Type::unknown) | ||||
|             Type::from(tuple.to_class_type(db)) | ||||
|         }; | ||||
| 
 | ||||
|         // HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
 | ||||
|  | @ -8619,17 +8615,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|         let value_node = subscript.value.as_ref(); | ||||
| 
 | ||||
|         let inferred = match (value_ty, slice_ty) { | ||||
|             (Type::NominalInstance(instance), _) | ||||
|                 if instance.class.is_known(db, KnownClass::VersionInfo) => | ||||
|             { | ||||
|                 Some(self.infer_subscript_expression_types( | ||||
|                     subscript, | ||||
|                     Type::version_info_tuple(db), | ||||
|                     slice_ty, | ||||
|                     expr_context, | ||||
|                 )) | ||||
|             } | ||||
| 
 | ||||
|             (Type::Union(union), _) => Some(union.map(db, |element| { | ||||
|                 self.infer_subscript_expression_types(subscript, *element, slice_ty, expr_context) | ||||
|             })), | ||||
|  | @ -8643,9 +8628,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             } | ||||
| 
 | ||||
|             // Ex) Given `("a", "b", "c", "d")[1]`, return `"b"`
 | ||||
|             (Type::Tuple(tuple_ty), Type::IntLiteral(i64_int)) => { | ||||
|                 i32::try_from(i64_int).ok().map(|i32_int| { | ||||
|                     let tuple = tuple_ty.tuple(db); | ||||
|             (Type::NominalInstance(nominal), Type::IntLiteral(i64_int)) => nominal | ||||
|                 .tuple_spec(db) | ||||
|                 .and_then(|tuple| Some((tuple, i32::try_from(i64_int).ok()?))) | ||||
|                 .map(|(tuple, i32_int)| { | ||||
|                     tuple.py_index(db, i32_int).unwrap_or_else(|_| { | ||||
|                         report_index_out_of_bounds( | ||||
|                             context, | ||||
|  | @ -8657,26 +8643,29 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                         ); | ||||
|                         Type::unknown() | ||||
|                     }) | ||||
|                 }) | ||||
|             } | ||||
|                 }), | ||||
| 
 | ||||
|             // Ex) Given `("a", 1, Null)[0:2]`, return `("a", 1)`
 | ||||
|             (Type::Tuple(tuple_ty), Type::NominalInstance(NominalInstanceType { class, .. })) => { | ||||
|                 class | ||||
|                     .slice_literal(db) | ||||
|                     .map(|SliceLiteral { start, stop, step }| { | ||||
|                         let TupleSpec::Fixed(tuple) = tuple_ty.tuple(db) else { | ||||
|                             return todo_type!("slice into variable-length tuple"); | ||||
|                         }; | ||||
| 
 | ||||
|             ( | ||||
|                 Type::NominalInstance(maybe_tuple_nominal), | ||||
|                 Type::NominalInstance(maybe_slice_nominal), | ||||
|             ) => maybe_tuple_nominal | ||||
|                 .tuple_spec(db) | ||||
|                 .as_deref() | ||||
|                 .and_then(|tuple_spec| Some((tuple_spec, maybe_slice_nominal.slice_literal(db)?))) | ||||
|                 .map(|(tuple, SliceLiteral { start, stop, step })| match tuple { | ||||
|                     TupleSpec::Fixed(tuple) => { | ||||
|                         if let Ok(new_elements) = tuple.py_slice(db, start, stop, step) { | ||||
|                             Type::heterogeneous_tuple(db, new_elements) | ||||
|                         } else { | ||||
|                             report_slice_step_size_zero(context, value_node.into()); | ||||
|                             Type::unknown() | ||||
|                         } | ||||
|                     }) | ||||
|             } | ||||
|                     } | ||||
|                     TupleSpec::Variable(_) => { | ||||
|                         todo_type!("slice into variable-length tuple") | ||||
|                     } | ||||
|                 }), | ||||
| 
 | ||||
|             // Ex) Given `"value"[1]`, return `"a"`
 | ||||
|             (Type::StringLiteral(literal_ty), Type::IntLiteral(i64_int)) => { | ||||
|  | @ -8700,10 +8689,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             } | ||||
| 
 | ||||
|             // Ex) Given `"value"[1:3]`, return `"al"`
 | ||||
|             ( | ||||
|                 Type::StringLiteral(literal_ty), | ||||
|                 Type::NominalInstance(NominalInstanceType { class, .. }), | ||||
|             ) => class | ||||
|             (Type::StringLiteral(literal_ty), Type::NominalInstance(nominal)) => nominal | ||||
|                 .slice_literal(db) | ||||
|                 .map(|SliceLiteral { start, stop, step }| { | ||||
|                     let literal_value = literal_ty.value(db); | ||||
|  | @ -8740,10 +8726,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             } | ||||
| 
 | ||||
|             // Ex) Given `b"value"[1:3]`, return `b"al"`
 | ||||
|             ( | ||||
|                 Type::BytesLiteral(literal_ty), | ||||
|                 Type::NominalInstance(NominalInstanceType { class, .. }), | ||||
|             ) => class | ||||
|             (Type::BytesLiteral(literal_ty), Type::NominalInstance(nominal)) => nominal | ||||
|                 .slice_literal(db) | ||||
|                 .map(|SliceLiteral { start, stop, step }| { | ||||
|                     let literal_value = literal_ty.value(db); | ||||
|  | @ -8758,37 +8741,30 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                 }), | ||||
| 
 | ||||
|             // Ex) Given `"value"[True]`, return `"a"`
 | ||||
|             ( | ||||
|                 Type::Tuple(_) | Type::StringLiteral(_) | Type::BytesLiteral(_), | ||||
|                 Type::BooleanLiteral(bool), | ||||
|             ) => Some(self.infer_subscript_expression_types( | ||||
|                 subscript, | ||||
|                 value_ty, | ||||
|                 Type::IntLiteral(i64::from(bool)), | ||||
|                 expr_context, | ||||
|             )), | ||||
| 
 | ||||
|             (Type::SpecialForm(SpecialFormType::Protocol), Type::Tuple(typevars)) => { | ||||
|                 Some(match typevars.tuple(db) { | ||||
|                     TupleSpec::Fixed(typevars) => self | ||||
|                         .legacy_generic_class_context( | ||||
|                             value_node, | ||||
|                             typevars.elements_slice(), | ||||
|                             LegacyGenericBase::Protocol, | ||||
|                         ) | ||||
|                         .map(|context| { | ||||
|                             Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context)) | ||||
|                         }) | ||||
|                         .unwrap_or_else(GenericContextError::into_type), | ||||
|                     // TODO: emit a diagnostic
 | ||||
|                     TupleSpec::Variable(_) => Type::unknown(), | ||||
|                 }) | ||||
|             (Type::StringLiteral(_) | Type::BytesLiteral(_), Type::BooleanLiteral(bool)) => { | ||||
|                 Some(self.infer_subscript_expression_types( | ||||
|                     subscript, | ||||
|                     value_ty, | ||||
|                     Type::IntLiteral(i64::from(bool)), | ||||
|                     expr_context, | ||||
|                 )) | ||||
|             } | ||||
| 
 | ||||
|             (Type::SpecialForm(SpecialFormType::Protocol), typevar) => Some( | ||||
|             (Type::NominalInstance(nominal), Type::BooleanLiteral(bool)) | ||||
|                 if nominal.tuple_spec(db).is_some() => | ||||
|             { | ||||
|                 Some(self.infer_subscript_expression_types( | ||||
|                     subscript, | ||||
|                     value_ty, | ||||
|                     Type::IntLiteral(i64::from(bool)), | ||||
|                     expr_context, | ||||
|                 )) | ||||
|             } | ||||
| 
 | ||||
|             (Type::SpecialForm(SpecialFormType::Protocol), typevars) => Some( | ||||
|                 self.legacy_generic_class_context( | ||||
|                     value_node, | ||||
|                     std::slice::from_ref(&typevar), | ||||
|                     typevars, | ||||
|                     LegacyGenericBase::Protocol, | ||||
|                 ) | ||||
|                 .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedProtocol(context))) | ||||
|  | @ -8800,31 +8776,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                 Some(todo_type!("doubly-specialized typing.Protocol")) | ||||
|             } | ||||
| 
 | ||||
|             (Type::SpecialForm(SpecialFormType::Generic), Type::Tuple(typevars)) => { | ||||
|                 Some(match typevars.tuple(db) { | ||||
|                     TupleSpec::Fixed(typevars) => self | ||||
|                         .legacy_generic_class_context( | ||||
|                             value_node, | ||||
|                             typevars.elements_slice(), | ||||
|                             LegacyGenericBase::Generic, | ||||
|                         ) | ||||
|                         .map(|context| { | ||||
|                             Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context)) | ||||
|                         }) | ||||
|                         .unwrap_or_else(GenericContextError::into_type), | ||||
|                     // TODO: emit a diagnostic
 | ||||
|                     TupleSpec::Variable(_) => Type::unknown(), | ||||
|                 }) | ||||
|             } | ||||
| 
 | ||||
|             (Type::SpecialForm(SpecialFormType::Generic), typevar) => Some( | ||||
|                 self.legacy_generic_class_context( | ||||
|                     value_node, | ||||
|                     std::slice::from_ref(&typevar), | ||||
|                     LegacyGenericBase::Generic, | ||||
|                 ) | ||||
|                 .map(|context| Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context))) | ||||
|                 .unwrap_or_else(GenericContextError::into_type), | ||||
|             (Type::SpecialForm(SpecialFormType::Generic), typevars) => Some( | ||||
|                 self.legacy_generic_class_context(value_node, typevars, LegacyGenericBase::Generic) | ||||
|                     .map(|context| { | ||||
|                         Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(context)) | ||||
|                     }) | ||||
|                     .unwrap_or_else(GenericContextError::into_type), | ||||
|             ), | ||||
| 
 | ||||
|             (Type::KnownInstance(KnownInstanceType::SubscriptedGeneric(_)), _) => { | ||||
|  | @ -9010,9 +8967,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|     fn legacy_generic_class_context( | ||||
|         &self, | ||||
|         value_node: &ast::Expr, | ||||
|         typevars: &[Type<'db>], | ||||
|         typevars: Type<'db>, | ||||
|         origin: LegacyGenericBase, | ||||
|     ) -> Result<GenericContext<'db>, GenericContextError> { | ||||
|         let typevars_class_tuple_spec = typevars.exact_tuple_instance_spec(self.db()); | ||||
| 
 | ||||
|         let typevars = if let Some(tuple_spec) = typevars_class_tuple_spec.as_deref() { | ||||
|             match tuple_spec { | ||||
|                 Tuple::Fixed(typevars) => typevars.elements_slice(), | ||||
|                 // TODO: emit a diagnostic
 | ||||
|                 Tuple::Variable(_) => return Err(GenericContextError::VariadicTupleArguments), | ||||
|             } | ||||
|         } else { | ||||
|             std::slice::from_ref(&typevars) | ||||
|         }; | ||||
| 
 | ||||
|         let typevars: Result<FxOrderSet<_>, GenericContextError> = typevars | ||||
|             .iter() | ||||
|             .map(|typevar| match typevar { | ||||
|  | @ -9026,9 +8995,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                 ) | ||||
|                 .ok_or(GenericContextError::InvalidArgument), | ||||
|                 Type::Dynamic(DynamicType::TodoUnpack) => Err(GenericContextError::NotYetSupported), | ||||
|                 Type::NominalInstance(NominalInstanceType { class, .. }) | ||||
|                 Type::NominalInstance(nominal) | ||||
|                     if matches!( | ||||
|                         class.known(self.db()), | ||||
|                         nominal.class(self.db()).known(self.db()), | ||||
|                         Some(KnownClass::TypeVarTuple | KnownClass::ParamSpec) | ||||
|                     ) => | ||||
|                 { | ||||
|  | @ -9071,7 +9040,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|         let type_to_slice_argument = |ty: Option<Type<'db>>| match ty { | ||||
|             Some(ty @ (Type::IntLiteral(_) | Type::BooleanLiteral(_))) => SliceArg::Arg(ty), | ||||
|             Some(ty @ Type::NominalInstance(instance)) | ||||
|                 if instance.class.is_known(self.db(), KnownClass::NoneType) => | ||||
|                 if instance | ||||
|                     .class(self.db()) | ||||
|                     .is_known(self.db(), KnownClass::NoneType) => | ||||
|             { | ||||
|                 SliceArg::Arg(ty) | ||||
|             } | ||||
|  | @ -9984,7 +9955,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { | |||
|         } = starred; | ||||
| 
 | ||||
|         let starred_type = self.infer_type_expression(value); | ||||
|         if let Type::Tuple(_) = starred_type { | ||||
|         if starred_type.exact_tuple_instance_spec(self.db()).is_some() { | ||||
|             starred_type | ||||
|         } else { | ||||
|             todo_type!("PEP 646") | ||||
|  | @ -10042,7 +10013,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { | |||
|             } | ||||
| 
 | ||||
|             match element { | ||||
|                 ast::Expr::Starred(_) => !matches!(element_ty, Type::Tuple(_)), | ||||
|                 ast::Expr::Starred(_) => { | ||||
|                     element_ty.exact_tuple_instance_spec(builder.db()).is_none() | ||||
|                 } | ||||
|                 ast::Expr::Subscript(ast::ExprSubscript { value, .. }) => { | ||||
|                     let value_ty = if builder.deferred_state.in_string_annotation() { | ||||
|                         // Using `.expression_type` does not work in string annotations, because
 | ||||
|  | @ -10079,10 +10052,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { | |||
|                     let element_ty = self.infer_type_expression(element); | ||||
|                     return_todo |= | ||||
|                         element_could_alter_type_of_whole_tuple(element, element_ty, self); | ||||
| 
 | ||||
|                     if let ast::Expr::Starred(_) = element { | ||||
|                         if let Type::Tuple(inner_tuple) = element_ty { | ||||
|                             element_types = | ||||
|                                 element_types.concat(self.db(), inner_tuple.tuple(self.db())); | ||||
|                         if let Some(inner_tuple) = element_ty.exact_tuple_instance_spec(self.db()) { | ||||
|                             element_types = element_types.concat(self.db(), &inner_tuple); | ||||
|                         } else { | ||||
|                             // TODO: emit a diagnostic
 | ||||
|                         } | ||||
|  | @ -10941,8 +10914,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { | |||
|                     Type::Dynamic(DynamicType::TodoPEP695ParamSpec) => { | ||||
|                         return Some(Parameters::todo()); | ||||
|                     } | ||||
|                     Type::NominalInstance(NominalInstanceType { class, .. }) | ||||
|                         if class.is_known(self.db(), KnownClass::ParamSpec) => | ||||
|                     Type::NominalInstance(nominal) | ||||
|                         if nominal | ||||
|                             .class(self.db()) | ||||
|                             .is_known(self.db(), KnownClass::ParamSpec) => | ||||
|                     { | ||||
|                         return Some(Parameters::todo()); | ||||
|                     } | ||||
|  | @ -10966,6 +10941,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { | |||
| enum GenericContextError { | ||||
|     /// It's invalid to subscript `Generic` or `Protocol` with this type
 | ||||
|     InvalidArgument, | ||||
|     /// It's invalid to subscript `Generic` or `Protocol` with a variadic tuple type.
 | ||||
|     /// We should emit a diagnostic for this, but we don't yet.
 | ||||
|     VariadicTupleArguments, | ||||
|     /// It's valid to subscribe `Generic` or `Protocol` with this type,
 | ||||
|     /// but the type is not yet supported.
 | ||||
|     NotYetSupported, | ||||
|  | @ -10974,7 +10952,9 @@ enum GenericContextError { | |||
| impl GenericContextError { | ||||
|     const fn into_type<'db>(self) -> Type<'db> { | ||||
|         match self { | ||||
|             GenericContextError::InvalidArgument => Type::unknown(), | ||||
|             GenericContextError::InvalidArgument | GenericContextError::VariadicTupleArguments => { | ||||
|                 Type::unknown() | ||||
|             } | ||||
|             GenericContextError::NotYetSupported => todo_type!("ParamSpecs and TypeVarTuples"), | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| //! Instance types: both nominal and structural.
 | ||||
| 
 | ||||
| use std::borrow::Cow; | ||||
| use std::marker::PhantomData; | ||||
| 
 | ||||
| use super::protocol_class::ProtocolInterface; | ||||
|  | @ -9,35 +10,48 @@ use crate::semantic_index::definition::Definition; | |||
| use crate::types::cyclic::PairVisitor; | ||||
| use crate::types::enums::is_single_member_enum; | ||||
| use crate::types::protocol_class::walk_protocol_interface; | ||||
| use crate::types::tuple::TupleType; | ||||
| use crate::types::{DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypedDictType}; | ||||
| use crate::{Db, FxOrderSet}; | ||||
| use crate::types::tuple::{TupleSpec, TupleType}; | ||||
| use crate::types::{ | ||||
|     ClassBase, DynamicType, TypeMapping, TypeRelation, TypeTransformer, TypedDictType, UnionType, | ||||
| }; | ||||
| use crate::{Db, FxOrderSet, Program}; | ||||
| 
 | ||||
| pub(super) use synthesized_protocol::SynthesizedProtocolType; | ||||
| 
 | ||||
| impl<'db> Type<'db> { | ||||
|     pub(crate) fn instance(db: &'db dyn Db, class: ClassType<'db>) -> Self { | ||||
|         match (class, class.known(db)) { | ||||
|             (_, Some(KnownClass::Any)) => Self::Dynamic(DynamicType::Any), | ||||
|             (ClassType::NonGeneric(_), Some(KnownClass::Tuple)) => { | ||||
|                 Type::tuple(TupleType::homogeneous(db, Type::unknown())) | ||||
|             } | ||||
|             (ClassType::Generic(alias), Some(KnownClass::Tuple)) => { | ||||
|                 Self::tuple(TupleType::new(db, alias.specialization(db).tuple(db))) | ||||
|             } | ||||
|             _ => { | ||||
|                 let class_literal = class.class_literal(db).0; | ||||
|                 if class_literal.is_protocol(db) { | ||||
|                     Self::ProtocolInstance(ProtocolInstanceType::from_class(class)) | ||||
|                 } else if class_literal.is_typed_dict(db) { | ||||
|                     TypedDictType::from(db, class) | ||||
|                 } else { | ||||
|                     Self::NominalInstance(NominalInstanceType::from_class(class)) | ||||
|                 } | ||||
|         let (class_literal, specialization) = class.class_literal(db); | ||||
| 
 | ||||
|         match class_literal.known(db) { | ||||
|             Some(KnownClass::Any) => Type::Dynamic(DynamicType::Any), | ||||
|             Some(KnownClass::Tuple) => Type::tuple(TupleType::new( | ||||
|                 db, | ||||
|                 specialization | ||||
|                     .and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?))) | ||||
|                     .unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))) | ||||
|                     .as_ref(), | ||||
|             )), | ||||
|             _ if class_literal.is_protocol(db) => { | ||||
|                 Self::ProtocolInstance(ProtocolInstanceType::from_class(class)) | ||||
|             } | ||||
|             _ if class_literal.is_typed_dict(db) => TypedDictType::from(db, class), | ||||
|             _ => Type::non_tuple_instance(class), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self { | ||||
|         let Some(tuple) = tuple else { | ||||
|             return Type::Never; | ||||
|         }; | ||||
|         Type::NominalInstance(NominalInstanceType(NominalInstanceInner::ExactTuple(tuple))) | ||||
|     } | ||||
| 
 | ||||
|     /// **Private** helper function to create a `Type::NominalInstance` from a class that
 | ||||
|     /// is known not to be `Any`, a protocol class, or a typed dict class.
 | ||||
|     fn non_tuple_instance(class: ClassType<'db>) -> Self { | ||||
|         Type::NominalInstance(NominalInstanceType(NominalInstanceInner::NonTuple(class))) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) const fn into_nominal_instance(self) -> Option<NominalInstanceType<'db>> { | ||||
|         match self { | ||||
|             Type::NominalInstance(instance_type) => Some(instance_type), | ||||
|  | @ -76,42 +90,176 @@ impl<'db> Type<'db> { | |||
| 
 | ||||
| /// A type representing the set of runtime objects which are instances of a certain nominal class.
 | ||||
| #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)] | ||||
| pub struct NominalInstanceType<'db> { | ||||
|     pub(super) class: ClassType<'db>, | ||||
| 
 | ||||
| pub struct NominalInstanceType<'db>( | ||||
|     // Keep this field private, so that the only way of constructing `NominalInstanceType` instances
 | ||||
|     // is through the `Type::instance` constructor function.
 | ||||
|     _phantom: PhantomData<()>, | ||||
| } | ||||
|     NominalInstanceInner<'db>, | ||||
| ); | ||||
| 
 | ||||
| pub(super) fn walk_nominal_instance_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>( | ||||
|     db: &'db dyn Db, | ||||
|     nominal: NominalInstanceType<'db>, | ||||
|     visitor: &mut V, | ||||
| ) { | ||||
|     visitor.visit_type(db, nominal.class.into()); | ||||
|     visitor.visit_type(db, nominal.class(db).into()); | ||||
| } | ||||
| 
 | ||||
| impl<'db> NominalInstanceType<'db> { | ||||
|     // Keep this method private, so that the only way of constructing `NominalInstanceType`
 | ||||
|     // instances is through the `Type::instance` constructor function.
 | ||||
|     fn from_class(class: ClassType<'db>) -> Self { | ||||
|         Self { | ||||
|             class, | ||||
|             _phantom: PhantomData, | ||||
|     pub(super) fn class(&self, db: &'db dyn Db) -> ClassType<'db> { | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => tuple.to_class_type(db), | ||||
|             NominalInstanceInner::NonTuple(class) => class, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// If this is an instance type where the class has a tuple spec, returns the tuple spec.
 | ||||
|     ///
 | ||||
|     /// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
 | ||||
|     /// For a subclass of `tuple[int, str]`, it will return the same tuple spec.
 | ||||
|     pub(super) fn tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> { | ||||
|         fn own_tuple_spec_of_class<'db>( | ||||
|             db: &'db dyn Db, | ||||
|             class: ClassType<'db>, | ||||
|         ) -> Option<Cow<'db, TupleSpec<'db>>> { | ||||
|             let (class_literal, specialization) = class.class_literal(db); | ||||
|             match class_literal.known(db)? { | ||||
|                 KnownClass::Tuple => Some( | ||||
|                     specialization | ||||
|                         .and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?))) | ||||
|                         .unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))), | ||||
|                 ), | ||||
|                 KnownClass::VersionInfo => { | ||||
|                     let python_version = Program::get(db).python_version(db); | ||||
|                     let int_instance_ty = KnownClass::Int.to_instance(db); | ||||
| 
 | ||||
|                     // TODO: just grab this type from typeshed (it's a `sys._ReleaseLevel` type alias there)
 | ||||
|                     let release_level_ty = { | ||||
|                         let elements: Box<[Type<'db>]> = ["alpha", "beta", "candidate", "final"] | ||||
|                             .iter() | ||||
|                             .map(|level| Type::string_literal(db, level)) | ||||
|                             .collect(); | ||||
| 
 | ||||
|                         // For most unions, it's better to go via `UnionType::from_elements` or use `UnionBuilder`;
 | ||||
|                         // those techniques ensure that union elements are deduplicated and unions are eagerly simplified
 | ||||
|                         // into other types where necessary. Here, however, we know that there are no duplicates
 | ||||
|                         // in this union, so it's probably more efficient to use `UnionType::new()` directly.
 | ||||
|                         Type::Union(UnionType::new(db, elements)) | ||||
|                     }; | ||||
| 
 | ||||
|                     Some(Cow::Owned(TupleSpec::from_elements([ | ||||
|                         Type::IntLiteral(python_version.major.into()), | ||||
|                         Type::IntLiteral(python_version.minor.into()), | ||||
|                         int_instance_ty, | ||||
|                         release_level_ty, | ||||
|                         int_instance_ty, | ||||
|                     ]))) | ||||
|                 } | ||||
|                 _ => None, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))), | ||||
|             NominalInstanceInner::NonTuple(class) => { | ||||
|                 // Avoid an expensive MRO traversal for common stdlib classes.
 | ||||
|                 if class | ||||
|                     .known(db) | ||||
|                     .is_some_and(|known_class| !known_class.is_tuple_subclass()) | ||||
|                 { | ||||
|                     return None; | ||||
|                 } | ||||
|                 class | ||||
|                     .iter_mro(db) | ||||
|                     .filter_map(ClassBase::into_class) | ||||
|                     .find_map(|class| own_tuple_spec_of_class(db, class)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Return `true` if this type represents instances of the class `builtins.object`.
 | ||||
|     pub(super) fn is_object(self, db: &'db dyn Db) -> bool { | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(_) => false, | ||||
|             NominalInstanceInner::NonTuple(class) => class.is_object(db), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// If this type is an *exact* tuple type (*not* a subclass of `tuple`), returns the
 | ||||
|     /// tuple spec.
 | ||||
|     ///
 | ||||
|     /// You usually don't want to use this method, as you usually want to consider a subclass
 | ||||
|     /// of a tuple type in the same way as the `tuple` type itself. Only use this method if you
 | ||||
|     /// are certain that a *literal tuple* is required, and that a subclass of tuple will not
 | ||||
|     /// do.
 | ||||
|     ///
 | ||||
|     /// I.e., for the type `tuple[int, str]`, this will return the tuple spec `[int, str]`.
 | ||||
|     /// But for a subclass of `tuple[int, str]`, it will return `None`.
 | ||||
|     pub(super) fn own_tuple_spec(&self, db: &'db dyn Db) -> Option<Cow<'db, TupleSpec<'db>>> { | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => Some(Cow::Borrowed(tuple.tuple(db))), | ||||
|             NominalInstanceInner::NonTuple(_) => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// If this is a specialized instance of `slice`, returns a [`SliceLiteral`] describing it.
 | ||||
|     /// Otherwise returns `None`.
 | ||||
|     ///
 | ||||
|     /// The specialization must be one in which the typevars are solved as being statically known
 | ||||
|     /// integers or `None`.
 | ||||
|     pub(crate) fn slice_literal(self, db: &'db dyn Db) -> Option<SliceLiteral> { | ||||
|         let class = match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(_) => return None, | ||||
|             NominalInstanceInner::NonTuple(class) => class, | ||||
|         }; | ||||
|         let (class, Some(specialization)) = class.class_literal(db) else { | ||||
|             return None; | ||||
|         }; | ||||
|         if !class.is_known(db, KnownClass::Slice) { | ||||
|             return None; | ||||
|         } | ||||
|         let [start, stop, step] = specialization.types(db) else { | ||||
|             return None; | ||||
|         }; | ||||
| 
 | ||||
|         let to_u32 = |ty: &Type<'db>| match ty { | ||||
|             Type::IntLiteral(n) => i32::try_from(*n).map(Some).ok(), | ||||
|             Type::BooleanLiteral(b) => Some(Some(i32::from(*b))), | ||||
|             Type::NominalInstance(instance) | ||||
|                 if instance.class(db).is_known(db, KnownClass::NoneType) => | ||||
|             { | ||||
|                 Some(None) | ||||
|             } | ||||
|             _ => None, | ||||
|         }; | ||||
|         Some(SliceLiteral { | ||||
|             start: to_u32(start)?, | ||||
|             stop: to_u32(stop)?, | ||||
|             step: to_u32(step)?, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn normalized_impl( | ||||
|         self, | ||||
|         db: &'db dyn Db, | ||||
|         visitor: &mut TypeTransformer<'db>, | ||||
|     ) -> Self { | ||||
|         Self::from_class(self.class.normalized_impl(db, visitor)) | ||||
|     ) -> Type<'db> { | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => { | ||||
|                 Type::tuple(tuple.normalized_impl(db, visitor)) | ||||
|             } | ||||
|             NominalInstanceInner::NonTuple(class) => { | ||||
|                 Type::non_tuple_instance(class.normalized_impl(db, visitor)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Self { | ||||
|         Self::from_class(self.class.materialize(db, variance)) | ||||
|     pub(super) fn materialize(self, db: &'db dyn Db, variance: TypeVarVariance) -> Type<'db> { | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => Type::tuple(tuple.materialize(db, variance)), | ||||
|             NominalInstanceInner::NonTuple(class) => { | ||||
|                 Type::non_tuple_instance(class.materialize(db, variance)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn has_relation_to( | ||||
|  | @ -120,41 +268,93 @@ impl<'db> NominalInstanceType<'db> { | |||
|         other: Self, | ||||
|         relation: TypeRelation, | ||||
|     ) -> bool { | ||||
|         self.class.has_relation_to(db, other.class, relation) | ||||
|         match (self.0, other.0) { | ||||
|             ( | ||||
|                 NominalInstanceInner::ExactTuple(tuple1), | ||||
|                 NominalInstanceInner::ExactTuple(tuple2), | ||||
|             ) => tuple1.has_relation_to(db, tuple2, relation), | ||||
|             _ => self | ||||
|                 .class(db) | ||||
|                 .has_relation_to(db, other.class(db), relation), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_equivalent_to(self, db: &'db dyn Db, other: Self) -> bool { | ||||
|         self.class.is_equivalent_to(db, other.class) | ||||
|         match (self.0, other.0) { | ||||
|             ( | ||||
|                 NominalInstanceInner::ExactTuple(tuple1), | ||||
|                 NominalInstanceInner::ExactTuple(tuple2), | ||||
|             ) => tuple1.is_equivalent_to(db, tuple2), | ||||
|             (NominalInstanceInner::NonTuple(class1), NominalInstanceInner::NonTuple(class2)) => { | ||||
|                 class1.is_equivalent_to(db, class2) | ||||
|             } | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_disjoint_from_impl(self, db: &'db dyn Db, other: Self) -> bool { | ||||
|         !self.class.could_coexist_in_mro_with(db, other.class) | ||||
|     pub(super) fn is_disjoint_from_impl( | ||||
|         self, | ||||
|         db: &'db dyn Db, | ||||
|         other: Self, | ||||
|         visitor: &mut PairVisitor<'db>, | ||||
|     ) -> bool { | ||||
|         let self_spec = self.tuple_spec(db); | ||||
|         if let Some(self_spec) = self_spec.as_deref() { | ||||
|             let other_spec = other.tuple_spec(db); | ||||
|             if let Some(other_spec) = other_spec.as_deref() { | ||||
|                 if self_spec.is_disjoint_from_impl(db, other_spec, visitor) { | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         !self | ||||
|             .class(db) | ||||
|             .could_coexist_in_mro_with(db, other.class(db)) | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_singleton(self, db: &'db dyn Db) -> bool { | ||||
|         self.class | ||||
|             .known(db) | ||||
|             .map(KnownClass::is_singleton) | ||||
|             .unwrap_or_else(|| is_single_member_enum(db, self.class.class_literal(db).0)) | ||||
|         match self.0 { | ||||
|             // The empty tuple is a singleton on CPython and PyPy, but not on other Python
 | ||||
|             // implementations such as GraalPy. Its *use* as a singleton is discouraged and
 | ||||
|             // should not be relied on for type narrowing, so we do not treat it as one.
 | ||||
|             // See:
 | ||||
|             // https://docs.python.org/3/reference/expressions.html#parenthesized-forms
 | ||||
|             NominalInstanceInner::ExactTuple(_) => false, | ||||
|             NominalInstanceInner::NonTuple(class) => class | ||||
|                 .known(db) | ||||
|                 .map(KnownClass::is_singleton) | ||||
|                 .unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_single_valued(self, db: &'db dyn Db) -> bool { | ||||
|         self.class | ||||
|             .known(db) | ||||
|             .map(KnownClass::is_single_valued) | ||||
|             .unwrap_or_else(|| is_single_member_enum(db, self.class.class_literal(db).0)) | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => tuple.is_single_valued(db), | ||||
|             NominalInstanceInner::NonTuple(class) => class | ||||
|                 .known(db) | ||||
|                 .and_then(KnownClass::is_single_valued) | ||||
|                 .or_else(|| Some(self.tuple_spec(db)?.is_single_valued(db))) | ||||
|                 .unwrap_or_else(|| is_single_member_enum(db, class.class_literal(db).0)), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn to_meta_type(self, db: &'db dyn Db) -> Type<'db> { | ||||
|         SubclassOfType::from(db, self.class) | ||||
|         SubclassOfType::from(db, self.class(db)) | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn apply_type_mapping<'a>( | ||||
|         self, | ||||
|         db: &'db dyn Db, | ||||
|         type_mapping: &TypeMapping<'a, 'db>, | ||||
|     ) -> Self { | ||||
|         Self::from_class(self.class.apply_type_mapping(db, type_mapping)) | ||||
|     ) -> Type<'db> { | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => { | ||||
|                 Type::tuple(tuple.apply_type_mapping(db, type_mapping)) | ||||
|             } | ||||
|             NominalInstanceInner::NonTuple(class) => { | ||||
|                 Type::non_tuple_instance(class.apply_type_mapping(db, type_mapping)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn find_legacy_typevars( | ||||
|  | @ -163,8 +363,14 @@ impl<'db> NominalInstanceType<'db> { | |||
|         binding_context: Option<Definition<'db>>, | ||||
|         typevars: &mut FxOrderSet<BoundTypeVarInstance<'db>>, | ||||
|     ) { | ||||
|         self.class | ||||
|             .find_legacy_typevars(db, binding_context, typevars); | ||||
|         match self.0 { | ||||
|             NominalInstanceInner::ExactTuple(tuple) => { | ||||
|                 tuple.find_legacy_typevars(db, binding_context, typevars); | ||||
|             } | ||||
|             NominalInstanceInner::NonTuple(class) => { | ||||
|                 class.find_legacy_typevars(db, binding_context, typevars); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -174,6 +380,30 @@ impl<'db> From<NominalInstanceType<'db>> for Type<'db> { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| /// [`NominalInstanceType`] is split into two variants internally as a pure
 | ||||
| /// optimization to avoid having to materialize the [`ClassType`] for tuple
 | ||||
| /// instances where it would be unnecessary (this is somewhat expensive!).
 | ||||
| #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, salsa::Update, get_size2::GetSize)] | ||||
| enum NominalInstanceInner<'db> { | ||||
|     /// A tuple type, e.g. `tuple[int, str]`.
 | ||||
|     ///
 | ||||
|     /// Note that the type `tuple[int, str]` includes subtypes of `tuple[int, str]`,
 | ||||
|     /// but those subtypes would be represented using the `NonTuple` variant.
 | ||||
|     ExactTuple(TupleType<'db>), | ||||
|     /// Any instance type that does not represent some kind of instance of the
 | ||||
|     /// builtin `tuple` class.
 | ||||
|     ///
 | ||||
|     /// This variant includes types that are subtypes of "exact tuple" types,
 | ||||
|     /// because they represent "all instances of a class that is a tuple subclass".
 | ||||
|     NonTuple(ClassType<'db>), | ||||
| } | ||||
| 
 | ||||
| pub(crate) struct SliceLiteral { | ||||
|     pub(crate) start: Option<i32>, | ||||
|     pub(crate) stop: Option<i32>, | ||||
|     pub(crate) step: Option<i32>, | ||||
| } | ||||
| 
 | ||||
| /// A `ProtocolInstanceType` represents the set of all possible runtime objects
 | ||||
| /// that conform to the interface described by a certain protocol.
 | ||||
| #[derive(
 | ||||
|  | @ -253,7 +483,7 @@ impl<'db> ProtocolInstanceType<'db> { | |||
|         db: &'db dyn Db, | ||||
|         visitor: &mut TypeTransformer<'db>, | ||||
|     ) -> Type<'db> { | ||||
|         let object = KnownClass::Object.to_instance(db); | ||||
|         let object = Type::object(db); | ||||
|         if object.satisfies_protocol(db, self, TypeRelation::Subtyping) { | ||||
|             return object; | ||||
|         } | ||||
|  |  | |||
|  | @ -182,14 +182,6 @@ impl ClassInfoConstraintFunction { | |||
|         }; | ||||
| 
 | ||||
|         match classinfo { | ||||
|             Type::Tuple(tuple) => UnionType::try_from_elements( | ||||
|                 db, | ||||
|                 tuple | ||||
|                     .tuple(db) | ||||
|                     .all_elements() | ||||
|                     .copied() | ||||
|                     .map(|element| self.generate_constraint(db, element)), | ||||
|             ), | ||||
|             Type::ClassLiteral(class_literal) => { | ||||
|                 // At runtime (on Python 3.11+), this will return `True` for classes that actually
 | ||||
|                 // do inherit `typing.Any` and `False` otherwise. We could accurately model that?
 | ||||
|  | @ -236,6 +228,15 @@ impl ClassInfoConstraintFunction { | |||
|             // e.g. `isinstance(x, list[int])` fails at runtime.
 | ||||
|             Type::GenericAlias(_) => None, | ||||
| 
 | ||||
|             Type::NominalInstance(nominal) => nominal.tuple_spec(db).and_then(|tuple| { | ||||
|                 UnionType::try_from_elements( | ||||
|                     db, | ||||
|                     tuple | ||||
|                         .all_elements() | ||||
|                         .map(|element| self.generate_constraint(db, *element)), | ||||
|                 ) | ||||
|             }), | ||||
| 
 | ||||
|             Type::AlwaysFalsy | ||||
|             | Type::AlwaysTruthy | ||||
|             | Type::BooleanLiteral(_) | ||||
|  | @ -252,7 +253,6 @@ impl ClassInfoConstraintFunction { | |||
|             | Type::ProtocolInstance(_) | ||||
|             | Type::PropertyInstance(_) | ||||
|             | Type::SpecialForm(_) | ||||
|             | Type::NominalInstance(_) | ||||
|             | Type::LiteralString | ||||
|             | Type::StringLiteral(_) | ||||
|             | Type::IntLiteral(_) | ||||
|  | @ -560,7 +560,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { | |||
|                     } | ||||
|                     // Treat `bool` as `Literal[True, False]`.
 | ||||
|                     Type::NominalInstance(instance) | ||||
|                         if instance.class.is_known(db, KnownClass::Bool) => | ||||
|                         if instance.class(db).is_known(db, KnownClass::Bool) => | ||||
|                     { | ||||
|                         UnionType::from_elements( | ||||
|                             db, | ||||
|  | @ -571,11 +571,11 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { | |||
|                     } | ||||
|                     // Treat enums as a union of their members.
 | ||||
|                     Type::NominalInstance(instance) | ||||
|                         if enum_metadata(db, instance.class.class_literal(db).0).is_some() => | ||||
|                         if enum_metadata(db, instance.class(db).class_literal(db).0).is_some() => | ||||
|                     { | ||||
|                         UnionType::from_elements( | ||||
|                             db, | ||||
|                             enum_member_literals(db, instance.class.class_literal(db).0, None) | ||||
|                             enum_member_literals(db, instance.class(db).class_literal(db).0, None) | ||||
|                                 .expect("Calling `enum_member_literals` on an enum class") | ||||
|                                 .map(|ty| filter_to_cannot_be_equal(db, ty, rhs_ty)), | ||||
|                         ) | ||||
|  | @ -602,7 +602,7 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { | |||
|     fn evaluate_expr_ne(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> { | ||||
|         match (lhs_ty, rhs_ty) { | ||||
|             (Type::NominalInstance(instance), Type::IntLiteral(i)) | ||||
|                 if instance.class.is_known(self.db, KnownClass::Bool) => | ||||
|                 if instance.class(self.db).is_known(self.db, KnownClass::Bool) => | ||||
|             { | ||||
|                 if i == 0 { | ||||
|                     Some(Type::BooleanLiteral(false).negate(self.db)) | ||||
|  | @ -623,20 +623,21 @@ impl<'db, 'ast> NarrowingConstraintsBuilder<'db, 'ast> { | |||
| 
 | ||||
|     fn evaluate_expr_in(&mut self, lhs_ty: Type<'db>, rhs_ty: Type<'db>) -> Option<Type<'db>> { | ||||
|         if lhs_ty.is_single_valued(self.db) || lhs_ty.is_union_of_single_valued(self.db) { | ||||
|             match rhs_ty { | ||||
|                 Type::Tuple(rhs_tuple) => Some(UnionType::from_elements( | ||||
|                     self.db, | ||||
|                     rhs_tuple.tuple(self.db).all_elements(), | ||||
|                 )), | ||||
| 
 | ||||
|                 Type::StringLiteral(string_literal) => Some(UnionType::from_elements( | ||||
|             if let Type::StringLiteral(string_literal) = rhs_ty { | ||||
|                 Some(UnionType::from_elements( | ||||
|                     self.db, | ||||
|                     string_literal | ||||
|                         .iter_each_char(self.db) | ||||
|                         .map(Type::StringLiteral), | ||||
|                 )), | ||||
| 
 | ||||
|                 _ => None, | ||||
|                 )) | ||||
|             } else if let Some(tuple_spec) = rhs_ty.tuple_instance_spec(self.db) { | ||||
|                 // N.B. Strictly speaking this is unsound, since a tuple subclass might override `__contains__`
 | ||||
|                 // but we'd still apply the narrowing here. This seems unlikely, however, and narrowing is
 | ||||
|                 // generally unsound in numerous ways anyway (attribute narrowing, subscript, narrowing,
 | ||||
|                 // narrowing of globals, etc.). So this doesn't seem worth worrying about too much.
 | ||||
|                 Some(UnionType::from_elements(self.db, tuple_spec.all_elements())) | ||||
|             } else { | ||||
|                 None | ||||
|             } | ||||
|         } else { | ||||
|             None | ||||
|  |  | |||
|  | @ -153,7 +153,7 @@ impl Ty { | |||
|                     .place | ||||
|                     .expect_type(); | ||||
|                 debug_assert!( | ||||
|                     matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class.class_literal(db).0)) | ||||
|                     matches!(ty, Type::NominalInstance(instance) if is_single_member_enum(db, instance.class(db).class_literal(db).0)) | ||||
|                 ); | ||||
|                 ty | ||||
|             } | ||||
|  |  | |||
|  | @ -23,12 +23,12 @@ use std::hash::Hash; | |||
| use itertools::{Either, EitherOrBoth, Itertools}; | ||||
| 
 | ||||
| use crate::semantic_index::definition::Definition; | ||||
| use crate::types::Truthiness; | ||||
| use crate::types::class::{ClassType, KnownClass}; | ||||
| use crate::types::{ | ||||
|     BoundTypeVarInstance, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarVariance, | ||||
|     UnionBuilder, UnionType, cyclic::PairVisitor, | ||||
| }; | ||||
| use crate::types::{SubclassOfType, Truthiness}; | ||||
| use crate::util::subscript::{Nth, OutOfBoundsError, PyIndex, PySlice, StepSizeZeroError}; | ||||
| use crate::{Db, FxOrderSet}; | ||||
| 
 | ||||
|  | @ -146,13 +146,6 @@ pub(super) fn walk_tuple_type<'db, V: super::visitor::TypeVisitor<'db> + ?Sized> | |||
| impl get_size2::GetSize for TupleType<'_> {} | ||||
| 
 | ||||
| impl<'db> Type<'db> { | ||||
|     pub(crate) fn tuple(tuple: Option<TupleType<'db>>) -> Self { | ||||
|         let Some(tuple) = tuple else { | ||||
|             return Type::Never; | ||||
|         }; | ||||
|         Self::Tuple(tuple) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn homogeneous_tuple(db: &'db dyn Db, element: Type<'db>) -> Self { | ||||
|         Type::tuple(TupleType::homogeneous(db, element)) | ||||
|     } | ||||
|  | @ -169,7 +162,7 @@ impl<'db> Type<'db> { | |||
|     } | ||||
| 
 | ||||
|     pub(crate) fn empty_tuple(db: &'db dyn Db) -> Self { | ||||
|         Type::Tuple(TupleType::empty(db)) | ||||
|         Type::tuple(Some(TupleType::empty(db))) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -227,23 +220,22 @@ impl<'db> TupleType<'db> { | |||
|         TupleType::new(db, TupleSpec::homogeneous(element)) | ||||
|     } | ||||
| 
 | ||||
|     // N.B. If this method is not Salsa-tracked, we take 10 minutes to check
 | ||||
|     // `static-frame` as part of a mypy_primer run! This is because it's called
 | ||||
|     // from `NominalInstanceType::class()`, which is a very hot method.
 | ||||
|     #[salsa::tracked] | ||||
|     pub(crate) fn to_class_type(self, db: &'db dyn Db) -> Option<ClassType<'db>> { | ||||
|         KnownClass::Tuple | ||||
|     pub(crate) fn to_class_type(self, db: &'db dyn Db) -> ClassType<'db> { | ||||
|         let tuple_class = KnownClass::Tuple | ||||
|             .try_to_class_literal(db) | ||||
|             .and_then(|class_literal| match class_literal.generic_context(db) { | ||||
|                 None => Some(ClassType::NonGeneric(class_literal)), | ||||
|                 Some(generic_context) if generic_context.variables(db).len() != 1 => None, | ||||
|                 Some(generic_context) => Some( | ||||
|                     class_literal | ||||
|                         .apply_specialization(db, |_| generic_context.specialize_tuple(db, self)), | ||||
|                 ), | ||||
|             }) | ||||
|     } | ||||
|             .expect("Typeshed should always have a `tuple` class in `builtins.pyi`"); | ||||
| 
 | ||||
|     pub(crate) fn to_subclass_of(self, db: &'db dyn Db) -> Option<Type<'db>> { | ||||
|         self.to_class_type(db) | ||||
|             .map(|class| SubclassOfType::from(db, class)) | ||||
|         tuple_class.apply_specialization(db, |generic_context| { | ||||
|             if generic_context.variables(db).len() == 1 { | ||||
|                 generic_context.specialize_tuple(db, self) | ||||
|             } else { | ||||
|                 generic_context.default_specialization(db, Some(KnownClass::Tuple)) | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     /// Return a normalized version of `self`.
 | ||||
|  | @ -294,23 +286,9 @@ impl<'db> TupleType<'db> { | |||
|         self.tuple(db).is_equivalent_to(db, other.tuple(db)) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn is_disjoint_from_impl( | ||||
|         self, | ||||
|         db: &'db dyn Db, | ||||
|         other: Self, | ||||
|         visitor: &mut PairVisitor<'db>, | ||||
|     ) -> bool { | ||||
|         self.tuple(db) | ||||
|             .is_disjoint_from_impl(db, other.tuple(db), visitor) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn is_single_valued(self, db: &'db dyn Db) -> bool { | ||||
|         self.tuple(db).is_single_valued(db) | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn truthiness(self, db: &'db dyn Db) -> Truthiness { | ||||
|         self.tuple(db).truthiness() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A tuple spec describes the contents of a tuple type, which might be fixed- or variable-length.
 | ||||
|  | @ -361,10 +339,6 @@ impl<T> FixedLengthTuple<T> { | |||
|         self.0.len() | ||||
|     } | ||||
| 
 | ||||
|     fn is_empty(&self) -> bool { | ||||
|         self.0.is_empty() | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn push(&mut self, element: T) { | ||||
|         self.0.push(element); | ||||
|     } | ||||
|  | @ -1029,10 +1003,6 @@ impl<T> Tuple<T> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) const fn is_variadic(&self) -> bool { | ||||
|         matches!(self, Tuple::Variable(_)) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the length of this tuple.
 | ||||
|     pub(crate) fn len(&self) -> TupleLength { | ||||
|         match self { | ||||
|  | @ -1052,13 +1022,6 @@ impl<T> Tuple<T> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn is_empty(&self) -> bool { | ||||
|         match self { | ||||
|             Tuple::Fixed(tuple) => tuple.is_empty(), | ||||
|             Tuple::Variable(_) => false, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub(crate) fn push(&mut self, element: T) { | ||||
|         match self { | ||||
|             Tuple::Fixed(tuple) => tuple.push(element), | ||||
|  | @ -1154,10 +1117,10 @@ impl<'db> Tuple<Type<'db>> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fn is_disjoint_from_impl( | ||||
|         &'db self, | ||||
|     pub(super) fn is_disjoint_from_impl( | ||||
|         &self, | ||||
|         db: &'db dyn Db, | ||||
|         other: &'db Self, | ||||
|         other: &Self, | ||||
|         visitor: &mut PairVisitor<'db>, | ||||
|     ) -> bool { | ||||
|         // Two tuples with an incompatible number of required elements must always be disjoint.
 | ||||
|  | @ -1172,12 +1135,15 @@ impl<'db> Tuple<Type<'db>> { | |||
| 
 | ||||
|         // If any of the required elements are pairwise disjoint, the tuples are disjoint as well.
 | ||||
|         #[allow(clippy::items_after_statements)] | ||||
|         fn any_disjoint<'db>( | ||||
|         fn any_disjoint<'s, 'db>( | ||||
|             db: &'db dyn Db, | ||||
|             a: impl IntoIterator<Item = &'db Type<'db>>, | ||||
|             b: impl IntoIterator<Item = &'db Type<'db>>, | ||||
|             a: impl IntoIterator<Item = &'s Type<'db>>, | ||||
|             b: impl IntoIterator<Item = &'s Type<'db>>, | ||||
|             visitor: &mut PairVisitor<'db>, | ||||
|         ) -> bool { | ||||
|         ) -> bool | ||||
|         where | ||||
|             'db: 's, | ||||
|         { | ||||
|             a.into_iter().zip(b).any(|(self_element, other_element)| { | ||||
|                 self_element.is_disjoint_from_impl(db, *other_element, visitor) | ||||
|             }) | ||||
|  | @ -1231,7 +1197,7 @@ impl<'db> Tuple<Type<'db>> { | |||
|         false | ||||
|     } | ||||
| 
 | ||||
|     fn is_single_valued(&self, db: &'db dyn Db) -> bool { | ||||
|     pub(crate) fn is_single_valued(&self, db: &'db dyn Db) -> bool { | ||||
|         match self { | ||||
|             Tuple::Fixed(tuple) => tuple.is_single_valued(db), | ||||
|             Tuple::Variable(_) => false, | ||||
|  |  | |||
|  | @ -100,10 +100,6 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( | |||
|         (Type::Callable(_), _) => Ordering::Less, | ||||
|         (_, Type::Callable(_)) => Ordering::Greater, | ||||
| 
 | ||||
|         (Type::Tuple(left), Type::Tuple(right)) => left.cmp(right), | ||||
|         (Type::Tuple(_), _) => Ordering::Less, | ||||
|         (_, Type::Tuple(_)) => Ordering::Greater, | ||||
| 
 | ||||
|         (Type::ModuleLiteral(left), Type::ModuleLiteral(right)) => left.cmp(right), | ||||
|         (Type::ModuleLiteral(_), _) => Ordering::Less, | ||||
|         (_, Type::ModuleLiteral(_)) => Ordering::Greater, | ||||
|  | @ -134,7 +130,9 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( | |||
|         (Type::TypeIs(_), _) => Ordering::Less, | ||||
|         (_, Type::TypeIs(_)) => Ordering::Greater, | ||||
| 
 | ||||
|         (Type::NominalInstance(left), Type::NominalInstance(right)) => left.class.cmp(&right.class), | ||||
|         (Type::NominalInstance(left), Type::NominalInstance(right)) => { | ||||
|             left.class(db).cmp(&right.class(db)) | ||||
|         } | ||||
|         (Type::NominalInstance(_), _) => Ordering::Less, | ||||
|         (_, Type::NominalInstance(_)) => Ordering::Greater, | ||||
| 
 | ||||
|  | @ -178,7 +176,7 @@ pub(super) fn union_or_intersection_elements_ordering<'db>( | |||
|                 (SuperOwnerKind::Class(_), _) => Ordering::Less, | ||||
|                 (_, SuperOwnerKind::Class(_)) => Ordering::Greater, | ||||
|                 (SuperOwnerKind::Instance(left), SuperOwnerKind::Instance(right)) => { | ||||
|                     left.class.cmp(&right.class) | ||||
|                     left.class(db).cmp(&right.class(db)) | ||||
|                 } | ||||
|                 (SuperOwnerKind::Instance(_), _) => Ordering::Less, | ||||
|                 (_, SuperOwnerKind::Instance(_)) => Ordering::Greater, | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ use crate::{ | |||
|         function::{FunctionType, walk_function_type}, | ||||
|         instance::{walk_nominal_instance_type, walk_protocol_instance_type}, | ||||
|         subclass_of::walk_subclass_of_type, | ||||
|         tuple::{TupleType, walk_tuple_type}, | ||||
|         walk_bound_method_type, walk_bound_super_type, walk_bound_type_var_type, | ||||
|         walk_callable_type, walk_intersection_type, walk_known_instance_type, | ||||
|         walk_method_wrapper_type, walk_property_instance_type, walk_type_alias_type, | ||||
|  | @ -33,10 +32,6 @@ pub(crate) trait TypeVisitor<'db> { | |||
|         walk_intersection_type(db, intersection, self); | ||||
|     } | ||||
| 
 | ||||
|     fn visit_tuple_type(&mut self, db: &'db dyn Db, tuple: TupleType<'db>) { | ||||
|         walk_tuple_type(db, tuple, self); | ||||
|     } | ||||
| 
 | ||||
|     fn visit_callable_type(&mut self, db: &'db dyn Db, callable: CallableType<'db>) { | ||||
|         walk_callable_type(db, callable, self); | ||||
|     } | ||||
|  | @ -127,7 +122,6 @@ pub(crate) trait TypeVisitor<'db> { | |||
| enum NonAtomicType<'db> { | ||||
|     Union(UnionType<'db>), | ||||
|     Intersection(IntersectionType<'db>), | ||||
|     Tuple(TupleType<'db>), | ||||
|     FunctionLiteral(FunctionType<'db>), | ||||
|     BoundMethod(BoundMethodType<'db>), | ||||
|     BoundSuper(BoundSuperType<'db>), | ||||
|  | @ -177,7 +171,6 @@ impl<'db> From<Type<'db>> for TypeKind<'db> { | |||
|                 TypeKind::NonAtomic(NonAtomicType::Intersection(intersection)) | ||||
|             } | ||||
|             Type::Union(union) => TypeKind::NonAtomic(NonAtomicType::Union(union)), | ||||
|             Type::Tuple(tuple) => TypeKind::NonAtomic(NonAtomicType::Tuple(tuple)), | ||||
|             Type::BoundMethod(method) => TypeKind::NonAtomic(NonAtomicType::BoundMethod(method)), | ||||
|             Type::BoundSuper(bound_super) => { | ||||
|                 TypeKind::NonAtomic(NonAtomicType::BoundSuper(bound_super)) | ||||
|  | @ -224,7 +217,6 @@ fn walk_non_atomic_type<'db, V: TypeVisitor<'db> + ?Sized>( | |||
|             visitor.visit_intersection_type(db, intersection); | ||||
|         } | ||||
|         NonAtomicType::Union(union) => visitor.visit_union_type(db, union), | ||||
|         NonAtomicType::Tuple(tuple) => visitor.visit_tuple_type(db, tuple), | ||||
|         NonAtomicType::BoundMethod(method) => visitor.visit_bound_method_type(db, method), | ||||
|         NonAtomicType::BoundSuper(bound_super) => visitor.visit_bound_super_type(db, bound_super), | ||||
|         NonAtomicType::MethodWrapper(method_wrapper) => { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Alex Waygood
						Alex Waygood