@@ -2,6 +2,7 @@ use infer::nearest_enclosing_class;
22use itertools:: { Either , Itertools } ;
33use ruff_db:: parsed:: parsed_module;
44
5+ use std:: borrow:: Cow ;
56use std:: slice:: Iter ;
67
78use bitflags:: bitflags;
@@ -56,7 +57,7 @@ use crate::types::infer::infer_unpack_types;
5657use crate :: types:: mro:: { Mro , MroError , MroIterator } ;
5758pub ( crate ) use crate :: types:: narrow:: infer_narrowing_constraint;
5859use crate :: types:: signatures:: { Parameter , ParameterForm , Parameters , walk_signature} ;
59- use crate :: types:: tuple:: TupleType ;
60+ use crate :: types:: tuple:: { TupleSpec , TupleType } ;
6061pub use crate :: util:: diagnostics:: add_inferred_python_version_hint_to_diagnostic;
6162use crate :: { Db , FxOrderSet , Module , Program } ;
6263pub ( crate ) use class:: { ClassLiteral , ClassType , GenericAlias , KnownClass } ;
@@ -813,13 +814,6 @@ impl<'db> Type<'db> {
813814 . expect ( "Expected a Type::EnumLiteral variant" )
814815 }
815816
816- pub ( crate ) const fn into_tuple ( self ) -> Option < TupleType < ' db > > {
817- match self {
818- Type :: Tuple ( tuple_type) => Some ( tuple_type) ,
819- _ => None ,
820- }
821- }
822-
823817 /// Turn a class literal (`Type::ClassLiteral` or `Type::GenericAlias`) into a `ClassType`.
824818 /// Since a `ClassType` must be specialized, apply the default specialization to any
825819 /// unspecialized generic class literal.
@@ -4615,35 +4609,50 @@ impl<'db> Type<'db> {
46154609 }
46164610 }
46174611
4618- /// Returns the element type when iterating over `self`.
4612+ /// Returns a tuple spec describing the elements that are produced when iterating over `self`.
46194613 ///
46204614 /// This method should only be used outside of type checking because it omits any errors.
46214615 /// For type checking, use [`try_iterate`](Self::try_iterate) instead.
4622- fn iterate ( self , db : & ' db dyn Db ) -> Type < ' db > {
4616+ fn iterate ( self , db : & ' db dyn Db ) -> Cow < ' db , TupleSpec < ' db > > {
46234617 self . try_iterate ( db)
4624- . unwrap_or_else ( |err| err. fallback_element_type ( db) )
4618+ . unwrap_or_else ( |err| Cow :: Owned ( TupleSpec :: homogeneous ( err. fallback_element_type ( db) ) ) )
46254619 }
46264620
46274621 /// Given the type of an object that is iterated over in some way,
4628- /// return the type of objects that are yielded by that iteration.
4622+ /// return a tuple spec describing the type of objects that are yielded by that iteration.
46294623 ///
4630- /// E.g., for the following loop, given the type of `x`, infer the type of `y`:
4624+ /// E.g., for the following call, given the type of `x`, infer the types of the values that are
4625+ /// splatted into `y`'s positional arguments:
46314626 /// ```python
4632- /// for y in x:
4633- /// pass
4627+ /// y(*x)
46344628 /// ```
4635- fn try_iterate ( self , db : & ' db dyn Db ) -> Result < Type < ' db > , IterationError < ' db > > {
4636- if let Type :: Tuple ( tuple_type) = self {
4637- return Ok ( UnionType :: from_elements (
4638- db,
4639- tuple_type. tuple ( db) . all_elements ( ) ,
4640- ) ) ;
4641- }
4642-
4643- if let Type :: GenericAlias ( alias) = self {
4644- if alias. origin ( db) . is_tuple ( db) {
4645- return Ok ( todo_type ! ( "*tuple[] annotations" ) ) ;
4629+ fn try_iterate ( self , db : & ' db dyn Db ) -> Result < Cow < ' db , TupleSpec < ' db > > , IterationError < ' db > > {
4630+ match self {
4631+ Type :: Tuple ( tuple_type) => return Ok ( Cow :: Borrowed ( tuple_type. tuple ( db) ) ) ,
4632+ Type :: GenericAlias ( alias) if alias. origin ( db) . is_tuple ( db) => {
4633+ return Ok ( Cow :: Owned ( TupleSpec :: homogeneous ( todo_type ! (
4634+ "*tuple[] annotations"
4635+ ) ) ) ) ;
4636+ }
4637+ Type :: StringLiteral ( string_literal_ty) => {
4638+ // We could go further and deconstruct to an array of `StringLiteral`
4639+ // with each individual character, instead of just an array of
4640+ // `LiteralString`, but there would be a cost and it's not clear that
4641+ // it's worth it.
4642+ return Ok ( Cow :: Owned ( TupleSpec :: from_elements ( std:: iter:: repeat_n (
4643+ Type :: LiteralString ,
4644+ string_literal_ty. python_len ( db) ,
4645+ ) ) ) ) ;
4646+ }
4647+ Type :: Never => {
4648+ // The dunder logic below would have us return `tuple[Never, ...]`, which eagerly
4649+ // simplifies to `tuple[()]`. That will will cause us to emit false positives if we
4650+ // index into the tuple. Using `tuple[Unknown, ...]` avoids these false positives.
4651+ // TODO: Consider removing this special case, and instead hide the indexing
4652+ // diagnostic in unreachable code.
4653+ return Ok ( Cow :: Owned ( TupleSpec :: homogeneous ( Type :: unknown ( ) ) ) ) ;
46464654 }
4655+ _ => { }
46474656 }
46484657
46494658 let try_call_dunder_getitem = || {
@@ -4669,12 +4678,14 @@ impl<'db> Type<'db> {
46694678 Ok ( iterator) => {
46704679 // `__iter__` is definitely bound and calling it succeeds.
46714680 // See what calling `__next__` on the object returned by `__iter__` gives us...
4672- try_call_dunder_next_on_iterator ( iterator) . map_err ( |dunder_next_error| {
4673- IterationError :: IterReturnsInvalidIterator {
4674- iterator,
4675- dunder_next_error,
4676- }
4677- } )
4681+ try_call_dunder_next_on_iterator ( iterator)
4682+ . map ( |ty| Cow :: Owned ( TupleSpec :: homogeneous ( ty) ) )
4683+ . map_err (
4684+ |dunder_next_error| IterationError :: IterReturnsInvalidIterator {
4685+ iterator,
4686+ dunder_next_error,
4687+ } ,
4688+ )
46784689 }
46794690
46804691 // `__iter__` is possibly unbound...
@@ -4692,10 +4703,10 @@ impl<'db> Type<'db> {
46924703 // and the type returned by the `__getitem__` method.
46934704 //
46944705 // No diagnostic is emitted; iteration will always succeed!
4695- UnionType :: from_elements (
4706+ Cow :: Owned ( TupleSpec :: homogeneous ( UnionType :: from_elements (
46964707 db,
46974708 [ dunder_next_return, dunder_getitem_return_type] ,
4698- )
4709+ ) ) )
46994710 } )
47004711 . map_err ( |dunder_getitem_error| {
47014712 IterationError :: PossiblyUnboundIterAndGetitemError {
@@ -4718,13 +4729,13 @@ impl<'db> Type<'db> {
47184729 }
47194730
47204731 // There's no `__iter__` method. Try `__getitem__` instead...
4721- Err ( CallDunderError :: MethodNotAvailable ) => {
4722- try_call_dunder_getitem ( ) . map_err ( |dunder_getitem_error| {
4723- IterationError :: UnboundIterAndGetitemError {
4732+ Err ( CallDunderError :: MethodNotAvailable ) => try_call_dunder_getitem ( )
4733+ . map ( |ty| Cow :: Owned ( TupleSpec :: homogeneous ( ty) ) )
4734+ . map_err (
4735+ |dunder_getitem_error| IterationError :: UnboundIterAndGetitemError {
47244736 dunder_getitem_error,
4725- }
4726- } )
4727- }
4737+ } ,
4738+ ) ,
47284739 }
47294740 }
47304741
0 commit comments