Skip to content

Commit cfb020d

Browse files
committed
return tuple spec when iterating
1 parent fcdffe4 commit cfb020d

File tree

7 files changed

+104
-93
lines changed

7 files changed

+104
-93
lines changed

crates/ty_python_semantic/src/types.rs

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ use crate::types::infer::infer_unpack_types;
5656
use crate::types::mro::{Mro, MroError, MroIterator};
5757
pub(crate) use crate::types::narrow::infer_narrowing_constraint;
5858
use crate::types::signatures::{Parameter, ParameterForm, Parameters, walk_signature};
59-
use crate::types::tuple::TupleType;
59+
use crate::types::tuple::{TupleSpec, TupleType};
6060
pub use crate::util::diagnostics::add_inferred_python_version_hint_to_diagnostic;
6161
use crate::{Db, FxOrderSet, Module, Program};
6262
pub(crate) use class::{ClassLiteral, ClassType, GenericAlias, KnownClass};
@@ -4610,35 +4610,43 @@ impl<'db> Type<'db> {
46104610
}
46114611
}
46124612

4613-
/// Returns the element type when iterating over `self`.
4613+
/// Returns a tuple spec describing the elements that are produced when iterating over `self`.
46144614
///
46154615
/// This method should only be used outside of type checking because it omits any errors.
46164616
/// For type checking, use [`try_iterate`](Self::try_iterate) instead.
4617-
fn iterate(self, db: &'db dyn Db) -> Type<'db> {
4617+
fn iterate(self, db: &'db dyn Db) -> TupleSpec<'db> {
46184618
self.try_iterate(db)
4619-
.unwrap_or_else(|err| err.fallback_element_type(db))
4619+
.unwrap_or_else(|err| TupleSpec::homogeneous(err.fallback_element_type(db)))
46204620
}
46214621

46224622
/// Given the type of an object that is iterated over in some way,
4623-
/// return the type of objects that are yielded by that iteration.
4623+
/// return a tuple spec describing the type of objects that are yielded by that iteration.
46244624
///
4625-
/// E.g., for the following loop, given the type of `x`, infer the type of `y`:
4625+
/// E.g., for the following call, given the type of `x`, infer the types of the values that are
4626+
/// splatted into `y`'s positional arguments:
4627+
/// of `y`:
46264628
/// ```python
4627-
/// for y in x:
4628-
/// pass
4629+
/// y(*x)
46294630
/// ```
4630-
fn try_iterate(self, db: &'db dyn Db) -> Result<Type<'db>, IterationError<'db>> {
4631-
if let Type::Tuple(tuple_type) = self {
4632-
return Ok(UnionType::from_elements(
4633-
db,
4634-
tuple_type.tuple(db).all_elements(),
4635-
));
4636-
}
4637-
4638-
if let Type::GenericAlias(alias) = self {
4639-
if alias.origin(db).is_known(db, KnownClass::Tuple) {
4640-
return Ok(todo_type!("*tuple[] annotations"));
4641-
}
4631+
fn try_iterate(self, db: &'db dyn Db) -> Result<TupleSpec<'db>, IterationError<'db>> {
4632+
match self {
4633+
// XXX: Cow?
4634+
Type::Tuple(tuple_type) => return Ok(tuple_type.tuple(db).clone()),
4635+
Type::GenericAlias(alias) if alias.origin(db).is_known(db, KnownClass::Tuple) => {
4636+
return Ok(TupleSpec::homogeneous(todo_type!("*tuple[] annotations")));
4637+
}
4638+
Type::StringLiteral(string_literal_ty) => {
4639+
// We could go further and deconstruct to an array of `StringLiteral`
4640+
// with each individual character, instead of just an array of
4641+
// `LiteralString`, but there would be a cost and it's not clear that
4642+
// it's worth it.
4643+
return Ok(TupleSpec::from_elements(std::iter::repeat_n(
4644+
Type::LiteralString,
4645+
string_literal_ty.python_len(db),
4646+
)));
4647+
}
4648+
Type::LiteralString => return Ok(TupleSpec::homogeneous(Type::LiteralString)),
4649+
_ => {}
46424650
}
46434651

46444652
let try_call_dunder_getitem = || {
@@ -4664,12 +4672,14 @@ impl<'db> Type<'db> {
46644672
Ok(iterator) => {
46654673
// `__iter__` is definitely bound and calling it succeeds.
46664674
// See what calling `__next__` on the object returned by `__iter__` gives us...
4667-
try_call_dunder_next_on_iterator(iterator).map_err(|dunder_next_error| {
4668-
IterationError::IterReturnsInvalidIterator {
4669-
iterator,
4670-
dunder_next_error,
4671-
}
4672-
})
4675+
try_call_dunder_next_on_iterator(iterator)
4676+
.map(TupleSpec::homogeneous)
4677+
.map_err(
4678+
|dunder_next_error| IterationError::IterReturnsInvalidIterator {
4679+
iterator,
4680+
dunder_next_error,
4681+
},
4682+
)
46734683
}
46744684

46754685
// `__iter__` is possibly unbound...
@@ -4687,10 +4697,10 @@ impl<'db> Type<'db> {
46874697
// and the type returned by the `__getitem__` method.
46884698
//
46894699
// No diagnostic is emitted; iteration will always succeed!
4690-
UnionType::from_elements(
4700+
TupleSpec::homogeneous(UnionType::from_elements(
46914701
db,
46924702
[dunder_next_return, dunder_getitem_return_type],
4693-
)
4703+
))
46944704
})
46954705
.map_err(|dunder_getitem_error| {
46964706
IterationError::PossiblyUnboundIterAndGetitemError {
@@ -4713,13 +4723,13 @@ impl<'db> Type<'db> {
47134723
}
47144724

47154725
// There's no `__iter__` method. Try `__getitem__` instead...
4716-
Err(CallDunderError::MethodNotAvailable) => {
4717-
try_call_dunder_getitem().map_err(|dunder_getitem_error| {
4718-
IterationError::UnboundIterAndGetitemError {
4726+
Err(CallDunderError::MethodNotAvailable) => try_call_dunder_getitem()
4727+
.map(TupleSpec::homogeneous)
4728+
.map_err(
4729+
|dunder_getitem_error| IterationError::UnboundIterAndGetitemError {
47194730
dunder_getitem_error,
4720-
}
4721-
})
4722-
}
4731+
},
4732+
),
47234733
}
47244734
}
47254735

crates/ty_python_semantic/src/types/call/bind.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::types::function::{
2424
};
2525
use crate::types::generics::{Specialization, SpecializationBuilder, SpecializationError};
2626
use crate::types::signatures::{Parameter, ParameterForm, Parameters};
27-
use crate::types::tuple::TupleType;
27+
use crate::types::tuple::{TupleSpec, TupleType};
2828
use crate::types::{
2929
BoundMethodType, ClassLiteral, DataclassParams, KnownClass, KnownInstanceType,
3030
MethodWrapperKind, PropertyInstanceType, SpecialFormType, TypeMapping, UnionType,
@@ -969,15 +969,15 @@ impl<'db> Bindings<'db> {
969969
// but `tuple[Never, ...]` eagerly simplifies to `tuple[()]`,
970970
// which will cause us to emit false positives if we index into the tuple.
971971
// Using `tuple[Unknown, ...]` avoids these false positives.
972-
let specialization = if argument.is_never() {
973-
Type::unknown()
972+
let tuple_spec = if argument.is_never() {
973+
TupleSpec::homogeneous(Type::unknown())
974974
} else {
975975
argument.try_iterate(db).expect(
976976
"try_iterate() should not fail on a type \
977977
assignable to `Iterable`",
978978
)
979979
};
980-
TupleType::homogeneous(db, specialization)
980+
Type::tuple(TupleType::new(db, tuple_spec))
981981
});
982982
overload.set_return_type(overridden_return);
983983
}

crates/ty_python_semantic/src/types/class.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2057,7 +2057,8 @@ impl<'db> ClassLiteral<'db> {
20572057
index.expression(for_stmt.iterable(&module)),
20582058
);
20592059
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
2060-
let inferred_ty = iterable_ty.iterate(db);
2060+
let inferred_ty =
2061+
iterable_ty.iterate(db).homogeneous_element_type(db);
20612062

20622063
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
20632064
}
@@ -2115,7 +2116,8 @@ impl<'db> ClassLiteral<'db> {
21152116
index.expression(comprehension.iterable(&module)),
21162117
);
21172118
// TODO: Potential diagnostics resulting from the iterable are currently not reported.
2118-
let inferred_ty = iterable_ty.iterate(db);
2119+
let inferred_ty =
2120+
iterable_ty.iterate(db).homogeneous_element_type(db);
21192121

21202122
union_of_inferred_types = union_of_inferred_types.add(inferred_ty);
21212123
}

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ impl<'db> GenericContext<'db> {
194194
db: &'db dyn Db,
195195
tuple: TupleType<'db>,
196196
) -> Specialization<'db> {
197-
let element_type = UnionType::from_elements(db, tuple.tuple(db).all_elements());
197+
let element_type = tuple.tuple(db).homogeneous_element_type(db);
198198
Specialization::new(db, self, Box::from([element_type]), Some(tuple))
199199
}
200200

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4226,6 +4226,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
42264226
builder
42274227
.infer_standalone_expression(iter_expr)
42284228
.iterate(builder.db())
4229+
.homogeneous_element_type(builder.db())
42294230
});
42304231

42314232
self.infer_body(body);
@@ -4255,10 +4256,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
42554256
}
42564257
TargetKind::Single => {
42574258
let iterable_type = self.infer_standalone_expression(iterable);
4258-
iterable_type.try_iterate(self.db()).unwrap_or_else(|err| {
4259-
err.report_diagnostic(&self.context, iterable_type, iterable.into());
4260-
err.fallback_element_type(self.db())
4261-
})
4259+
iterable_type
4260+
.try_iterate(self.db())
4261+
.map(|tuple| tuple.homogeneous_element_type(self.db()))
4262+
.unwrap_or_else(|err| {
4263+
err.report_diagnostic(&self.context, iterable_type, iterable.into());
4264+
err.fallback_element_type(self.db())
4265+
})
42624266
}
42634267
}
42644268
};
@@ -5370,6 +5374,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
53705374
builder.infer_standalone_expression(iter_expr)
53715375
}
53725376
.iterate(builder.db())
5377+
.homogeneous_element_type(builder.db())
53735378
});
53745379
for expr in ifs {
53755380
self.infer_standalone_expression(expr);
@@ -5421,10 +5426,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
54215426
}
54225427
TargetKind::Single => {
54235428
let iterable_type = infer_iterable_type();
5424-
iterable_type.try_iterate(self.db()).unwrap_or_else(|err| {
5425-
err.report_diagnostic(&self.context, iterable_type, iterable.into());
5426-
err.fallback_element_type(self.db())
5427-
})
5429+
iterable_type
5430+
.try_iterate(self.db())
5431+
.map(|tuple| tuple.homogeneous_element_type(self.db()))
5432+
.unwrap_or_else(|err| {
5433+
err.report_diagnostic(&self.context, iterable_type, iterable.into());
5434+
err.fallback_element_type(self.db())
5435+
})
54285436
}
54295437
}
54305438
};
@@ -5753,10 +5761,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
57535761
} = starred;
57545762

57555763
let iterable_type = self.infer_expression(value);
5756-
iterable_type.try_iterate(self.db()).unwrap_or_else(|err| {
5757-
err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
5758-
err.fallback_element_type(self.db())
5759-
});
5764+
iterable_type
5765+
.try_iterate(self.db())
5766+
.map(|tuple| tuple.homogeneous_element_type(self.db()))
5767+
.unwrap_or_else(|err| {
5768+
err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
5769+
err.fallback_element_type(self.db())
5770+
});
57605771

57615772
// TODO
57625773
todo_type!("starred expression")
@@ -5780,10 +5791,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
57805791
} = yield_from;
57815792

57825793
let iterable_type = self.infer_expression(value);
5783-
iterable_type.try_iterate(self.db()).unwrap_or_else(|err| {
5784-
err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
5785-
err.fallback_element_type(self.db())
5786-
});
5794+
iterable_type
5795+
.try_iterate(self.db())
5796+
.map(|tuple| tuple.homogeneous_element_type(self.db()))
5797+
.unwrap_or_else(|err| {
5798+
err.report_diagnostic(&self.context, iterable_type, value.as_ref().into());
5799+
err.fallback_element_type(self.db())
5800+
});
57875801

57885802
// TODO get type from `ReturnType` of generator
57895803
todo_type!("Generic `typing.Generator` type")

crates/ty_python_semantic/src/types/tuple.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,10 @@ impl<T> Tuple<T> {
10061006
}
10071007

10081008
impl<'db> Tuple<Type<'db>> {
1009+
pub(crate) fn homogeneous_element_type(&self, db: &'db dyn Db) -> Type<'db> {
1010+
UnionType::from_elements(db, self.all_elements())
1011+
}
1012+
10091013
/// Concatenates another tuple to the end of this tuple, returning a new tuple.
10101014
pub(crate) fn concat(&self, db: &'db dyn Db, other: &Self) -> Self {
10111015
match self {

crates/ty_python_semantic/src/types/unpacker.rs

Lines changed: 17 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::borrow::Cow;
2-
31
use ruff_db::parsed::ParsedModuleRef;
42
use rustc_hash::FxHashMap;
53

@@ -8,7 +6,7 @@ use ruff_python_ast::{self as ast, AnyNodeRef};
86
use crate::Db;
97
use crate::semantic_index::ast_ids::node_key::ExpressionNodeKey;
108
use crate::semantic_index::place::ScopeId;
11-
use crate::types::tuple::{ResizeTupleError, Tuple, TupleLength, TupleUnpacker};
9+
use crate::types::tuple::{ResizeTupleError, Tuple, TupleLength, TupleSpec, TupleUnpacker};
1210
use crate::types::{Type, TypeCheckDiagnostics, infer_expression_types};
1311
use crate::unpack::{UnpackKind, UnpackValue};
1412

@@ -64,14 +62,17 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
6462
value_type
6563
}
6664
}
67-
UnpackKind::Iterable => value_type.try_iterate(self.db()).unwrap_or_else(|err| {
68-
err.report_diagnostic(
69-
&self.context,
70-
value_type,
71-
value.as_any_node_ref(self.db(), self.module()),
72-
);
73-
err.fallback_element_type(self.db())
74-
}),
65+
UnpackKind::Iterable => value_type
66+
.try_iterate(self.db())
67+
.map(|tuple| tuple.homogeneous_element_type(self.db()))
68+
.unwrap_or_else(|err| {
69+
err.report_diagnostic(
70+
&self.context,
71+
value_type,
72+
value.as_any_node_ref(self.db(), self.module()),
73+
);
74+
err.fallback_element_type(self.db())
75+
}),
7576
UnpackKind::ContextManager => value_type.try_enter(self.db()).unwrap_or_else(|err| {
7677
err.report_diagnostic(
7778
&self.context,
@@ -118,32 +119,12 @@ impl<'db, 'ast> Unpacker<'db, 'ast> {
118119
};
119120

120121
for ty in unpack_types.iter().copied() {
121-
let tuple = match ty {
122-
Type::Tuple(tuple_ty) => Cow::Borrowed(tuple_ty.tuple(self.db())),
123-
Type::StringLiteral(string_literal_ty) => {
124-
// We could go further and deconstruct to an array of `StringLiteral`
125-
// with each individual character, instead of just an array of
126-
// `LiteralString`, but there would be a cost and it's not clear that
127-
// it's worth it.
128-
Cow::Owned(Tuple::from_elements(std::iter::repeat_n(
129-
Type::LiteralString,
130-
string_literal_ty.python_len(self.db()),
131-
)))
132-
}
133-
Type::LiteralString => Cow::Owned(Tuple::homogeneous(Type::LiteralString)),
134-
_ => {
135-
// TODO: Update our iterator protocol machinery to return a tuple
136-
// describing the returned values in more detail, when we can.
137-
Cow::Owned(Tuple::homogeneous(
138-
ty.try_iterate(self.db()).unwrap_or_else(|err| {
139-
err.report_diagnostic(&self.context, ty, value_expr);
140-
err.fallback_element_type(self.db())
141-
}),
142-
))
143-
}
144-
};
122+
let tuple = ty.try_iterate(self.db()).unwrap_or_else(|err| {
123+
err.report_diagnostic(&self.context, ty, value_expr);
124+
TupleSpec::homogeneous(err.fallback_element_type(self.db()))
125+
});
145126

146-
if let Err(err) = unpacker.unpack_tuple(tuple.as_ref()) {
127+
if let Err(err) = unpacker.unpack_tuple(&tuple) {
147128
unpacker
148129
.unpack_tuple(&Tuple::homogeneous(Type::unknown()))
149130
.expect("adding a homogeneous tuple should always succeed");

0 commit comments

Comments
 (0)