mirror of
				https://github.com/astral-sh/ruff.git
				synced 2025-10-30 19:47:52 +00:00 
			
		
		
		
	[ty] Infer type[tuple[int, str]] as the meta-type of tuple[int, str] (#19741)
				
					
				
			This commit is contained in:
		
							parent
							
								
									bc6e8b58ce
								
							
						
					
					
						commit
						41207ec901
					
				
					 7 changed files with 71 additions and 21 deletions
				
			
		|  | @ -790,6 +790,13 @@ impl<'db> Type<'db> { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub const fn into_subclass_of(self) -> Option<SubclassOfType<'db>> { | ||||
|         match self { | ||||
|             Type::SubclassOf(subclass_of) => Some(subclass_of), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     #[track_caller] | ||||
|     pub fn expect_class_literal(self) -> ClassLiteral<'db> { | ||||
|         self.into_class_literal() | ||||
|  | @ -5496,10 +5503,8 @@ 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_class_type(db) | ||||
|                 .map(Type::from) | ||||
|                 .unwrap_or_else(Type::unknown), | ||||
| 
 | ||||
|                 .to_subclass_of(db) | ||||
|                 .unwrap_or_else(SubclassOfType::subclass_of_unknown), | ||||
|             Type::TypeVar(typevar) => match typevar.bound_or_constraints(db) { | ||||
|                 None => KnownClass::Type.to_instance(db), | ||||
|                 Some(TypeVarBoundOrConstraints::UpperBound(bound)) => bound.to_meta_type(db), | ||||
|  |  | |||
|  | @ -268,6 +268,10 @@ pub enum ClassType<'db> { | |||
| 
 | ||||
| #[salsa::tracked] | ||||
| impl<'db> ClassType<'db> { | ||||
|     pub(super) const fn is_not_generic(self) -> bool { | ||||
|         matches!(self, Self::NonGeneric(_)) | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn normalized_impl( | ||||
|         self, | ||||
|         db: &'db dyn Db, | ||||
|  |  | |||
|  | @ -143,6 +143,11 @@ impl<'db> AllMembers<'db> { | |||
|                 Type::ClassLiteral(class_literal) => { | ||||
|                     self.extend_with_class_members(db, ty, class_literal); | ||||
|                 } | ||||
|                 Type::SubclassOf(subclass_of) => { | ||||
|                     if let Some(class) = subclass_of.subclass_of().into_class() { | ||||
|                         self.extend_with_class_members(db, ty, class.class_literal(db).0); | ||||
|                     } | ||||
|                 } | ||||
|                 Type::GenericAlias(generic_alias) => { | ||||
|                     let class_literal = generic_alias.origin(db); | ||||
|                     self.extend_with_class_members(db, ty, class_literal); | ||||
|  |  | |||
|  | @ -6069,7 +6069,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             // the `try_call` path below.
 | ||||
|             // TODO: it should be possible to move these special cases into the `try_call_constructor`
 | ||||
|             // path instead, or even remove some entirely once we support overloads fully.
 | ||||
|             if !matches!( | ||||
|             let has_special_cased_constructor = matches!( | ||||
|                 class.known(self.db()), | ||||
|                 Some( | ||||
|                     KnownClass::Bool | ||||
|  | @ -6083,20 +6083,21 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|                         | KnownClass::TypeAliasType | ||||
|                         | KnownClass::Deprecated | ||||
|                 ) | ||||
|             ) | ||||
| 
 | ||||
|             // Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
 | ||||
|             // but constructor calls to `tuple[int]`, `tuple[int, ...]`, `tuple[int, *tuple[str, ...]]` (etc.)
 | ||||
|             // are handled by the default constructor-call logic (we synthesize a `__new__` method for them
 | ||||
|             // in `ClassType::own_class_member()`).
 | ||||
|             && (callable_type.is_generic_alias() || !class.is_known(self.db(), KnownClass::Tuple)) | ||||
|             ) || ( | ||||
|                 // Constructor calls to `tuple` and subclasses of `tuple` are handled in `Type::Bindings`,
 | ||||
|                 // but constructor calls to `tuple[int]`, `tuple[int, ...]`, `tuple[int, *tuple[str, ...]]` (etc.)
 | ||||
|                 // are handled by the default constructor-call logic (we synthesize a `__new__` method for them
 | ||||
|                 // in `ClassType::own_class_member()`).
 | ||||
|                 class.is_known(self.db(), KnownClass::Tuple) && class.is_not_generic() | ||||
|             ); | ||||
| 
 | ||||
|             // temporary special-casing for all subclasses of `enum.Enum`
 | ||||
|             // until we support the functional syntax for creating enum classes
 | ||||
|             && KnownClass::Enum | ||||
|                 .to_class_literal(self.db()) | ||||
|                 .to_class_type(self.db()) | ||||
|                 .is_none_or(|enum_class| !class.is_subclass_of(self.db(), enum_class)) | ||||
|             if !has_special_cased_constructor | ||||
|                 && KnownClass::Enum | ||||
|                     .to_class_literal(self.db()) | ||||
|                     .to_class_type(self.db()) | ||||
|                     .is_none_or(|enum_class| !class.is_subclass_of(self.db(), enum_class)) | ||||
|             { | ||||
|                 let argument_forms = vec![Some(ParameterForm::Value); call_arguments.len()]; | ||||
|                 self.infer_argument_types(arguments, &mut call_arguments, &argument_forms); | ||||
|  | @ -8478,6 +8479,15 @@ 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) | ||||
|         }; | ||||
| 
 | ||||
|         // HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
 | ||||
|         // subscript inference logic and treat this as an explicit specialization.
 | ||||
|         // TODO: Move this logic into a custom callable, and update `find_name_in_mro` to return
 | ||||
|  | @ -8486,8 +8496,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|         // special cases, too.
 | ||||
|         if let Type::ClassLiteral(class) = value_ty { | ||||
|             if class.is_tuple(self.db()) { | ||||
|                 return Type::tuple(self.infer_tuple_type_expression(slice)) | ||||
|                     .to_meta_type(self.db()); | ||||
|                 return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice)); | ||||
|             } | ||||
|             if let Some(generic_context) = class.generic_context(self.db()) { | ||||
|                 return self.infer_explicit_class_specialization( | ||||
|  | @ -8499,7 +8508,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { | |||
|             } | ||||
|         } | ||||
|         if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty { | ||||
|             return Type::tuple(self.infer_tuple_type_expression(slice)).to_meta_type(self.db()); | ||||
|             return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice)); | ||||
|         } | ||||
| 
 | ||||
|         let slice_ty = self.infer_expression(slice); | ||||
|  |  | |||
|  | @ -22,8 +22,8 @@ use std::hash::Hash; | |||
| 
 | ||||
| use itertools::{Either, EitherOrBoth, Itertools}; | ||||
| 
 | ||||
| use crate::types::Truthiness; | ||||
| use crate::types::class::{ClassType, KnownClass}; | ||||
| use crate::types::{SubclassOfType, Truthiness}; | ||||
| use crate::types::{ | ||||
|     Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarInstance, TypeVarVariance, | ||||
|     UnionBuilder, UnionType, cyclic::PairVisitor, | ||||
|  | @ -238,6 +238,11 @@ impl<'db> TupleType<'db> { | |||
|             }) | ||||
|     } | ||||
| 
 | ||||
|     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)) | ||||
|     } | ||||
| 
 | ||||
|     /// Return a normalized version of `self`.
 | ||||
|     ///
 | ||||
|     /// See [`Type::normalized`] for more details.
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Alex Waygood
						Alex Waygood