@@ -2,6 +2,7 @@ use infer::nearest_enclosing_class;
2
2
use itertools:: { Either , Itertools } ;
3
3
use ruff_db:: parsed:: parsed_module;
4
4
5
+ use std:: borrow:: Cow ;
5
6
use std:: slice:: Iter ;
6
7
7
8
use bitflags:: bitflags;
@@ -56,7 +57,7 @@ use crate::types::infer::infer_unpack_types;
56
57
use crate :: types:: mro:: { Mro , MroError , MroIterator } ;
57
58
pub ( crate ) use crate :: types:: narrow:: infer_narrowing_constraint;
58
59
use crate :: types:: signatures:: { Parameter , ParameterForm , Parameters , walk_signature} ;
59
- use crate :: types:: tuple:: TupleType ;
60
+ use crate :: types:: tuple:: { TupleSpec , TupleType } ;
60
61
pub use crate :: util:: diagnostics:: add_inferred_python_version_hint_to_diagnostic;
61
62
use crate :: { Db , FxOrderSet , Module , Program } ;
62
63
pub ( crate ) use class:: { ClassLiteral , ClassType , GenericAlias , KnownClass } ;
@@ -813,13 +814,6 @@ impl<'db> Type<'db> {
813
814
. expect ( "Expected a Type::EnumLiteral variant" )
814
815
}
815
816
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
-
823
817
/// Turn a class literal (`Type::ClassLiteral` or `Type::GenericAlias`) into a `ClassType`.
824
818
/// Since a `ClassType` must be specialized, apply the default specialization to any
825
819
/// unspecialized generic class literal.
@@ -4615,35 +4609,50 @@ impl<'db> Type<'db> {
4615
4609
}
4616
4610
}
4617
4611
4618
- /// Returns the element type when iterating over `self`.
4612
+ /// Returns a tuple spec describing the elements that are produced when iterating over `self`.
4619
4613
///
4620
4614
/// This method should only be used outside of type checking because it omits any errors.
4621
4615
/// 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 > > {
4623
4617
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) ) ) )
4625
4619
}
4626
4620
4627
4621
/// 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.
4629
4623
///
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:
4631
4626
/// ```python
4632
- /// for y in x:
4633
- /// pass
4627
+ /// y(*x)
4634
4628
/// ```
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 ( ) ) ) ) ;
4646
4654
}
4655
+ _ => { }
4647
4656
}
4648
4657
4649
4658
let try_call_dunder_getitem = || {
@@ -4669,12 +4678,14 @@ impl<'db> Type<'db> {
4669
4678
Ok ( iterator) => {
4670
4679
// `__iter__` is definitely bound and calling it succeeds.
4671
4680
// 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
+ )
4678
4689
}
4679
4690
4680
4691
// `__iter__` is possibly unbound...
@@ -4692,10 +4703,10 @@ impl<'db> Type<'db> {
4692
4703
// and the type returned by the `__getitem__` method.
4693
4704
//
4694
4705
// No diagnostic is emitted; iteration will always succeed!
4695
- UnionType :: from_elements (
4706
+ Cow :: Owned ( TupleSpec :: homogeneous ( UnionType :: from_elements (
4696
4707
db,
4697
4708
[ dunder_next_return, dunder_getitem_return_type] ,
4698
- )
4709
+ ) ) )
4699
4710
} )
4700
4711
. map_err ( |dunder_getitem_error| {
4701
4712
IterationError :: PossiblyUnboundIterAndGetitemError {
@@ -4718,13 +4729,13 @@ impl<'db> Type<'db> {
4718
4729
}
4719
4730
4720
4731
// 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 {
4724
4736
dunder_getitem_error,
4725
- }
4726
- } )
4727
- }
4737
+ } ,
4738
+ ) ,
4728
4739
}
4729
4740
}
4730
4741
0 commit comments