Skip to content

Commit c88a350

Browse files
committed
fix typing conformance suite regression
1 parent d7f08a4 commit c88a350

File tree

6 files changed

+63
-35
lines changed

6 files changed

+63
-35
lines changed

crates/ty_python_semantic/resources/mdtest/type_compendium/tuple.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,4 +477,23 @@ class NotAlwaysTruthyTuple(tuple[int]):
477477
t: tuple[int] = NotAlwaysTruthyTuple((1,))
478478
```
479479

480+
## Unspecialized
481+
482+
An unspecialized tuple is equivalent to `tuple[Any, ...]` and `tuple[Unknown, ...]`.
483+
484+
```py
485+
from typing_extensions import Any, assert_type
486+
from ty_extensions import Unknown, is_equivalent_to, static_assert
487+
488+
static_assert(is_equivalent_to(tuple[Any, ...], tuple[Unknown, ...]))
489+
490+
def f(x: tuple, y: tuple[Unknown, ...]):
491+
reveal_type(x) # revealed: tuple[Unknown, ...]
492+
assert_type(x, tuple[Any, ...])
493+
assert_type(x, tuple[Unknown, ...])
494+
reveal_type(y) # revealed: tuple[Unknown, ...]
495+
assert_type(y, tuple[Any, ...])
496+
assert_type(y, tuple[Unknown, ...])
497+
```
498+
480499
[not a singleton type]: https://discuss.python.org/t/should-we-specify-in-the-language-reference-that-the-empty-tuple-is-a-singleton/67957

crates/ty_python_semantic/src/types/class.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,8 @@ impl<'db> ClassType<'db> {
623623
match name {
624624
"__len__" if class_literal.is_tuple(db) => {
625625
let return_type = specialization
626-
.and_then(|spec| spec.tuple(db).len().into_fixed_length())
626+
.and_then(|spec| spec.tuple(db))
627+
.and_then(|tuple| tuple.len().into_fixed_length())
627628
.and_then(|len| i64::try_from(len).ok())
628629
.map(Type::IntLiteral)
629630
.unwrap_or_else(|| KnownClass::Int.to_instance(db));
@@ -633,17 +634,17 @@ impl<'db> ClassType<'db> {
633634

634635
"__bool__" if class_literal.is_tuple(db) => {
635636
let return_type = specialization
636-
.map(|spec| spec.tuple(db).truthiness().into_type(db))
637+
.and_then(|spec| spec.tuple(db))
638+
.map(|tuple| tuple.truthiness().into_type(db))
637639
.unwrap_or_else(|| KnownClass::Bool.to_instance(db));
638640

639641
synthesize_simple_tuple_method(return_type)
640642
}
641643

642644
"__getitem__" if class_literal.is_tuple(db) => {
643645
specialization
644-
.map(|spec| {
645-
let tuple = spec.tuple(db);
646-
646+
.and_then(|spec| spec.tuple(db))
647+
.map(|tuple| {
647648
let mut element_type_to_indices: FxIndexMap<Type<'db>, Vec<i64>> =
648649
FxIndexMap::default();
649650

@@ -806,11 +807,12 @@ impl<'db> ClassType<'db> {
806807
let mut iterable_parameter =
807808
Parameter::positional_only(Some(Name::new_static("iterable")));
808809

809-
match specialization {
810-
Some(spec) => {
810+
let tuple = specialization.and_then(|spec| spec.tuple(db));
811+
812+
match tuple {
813+
Some(tuple) => {
811814
// TODO: Once we support PEP 646 annotations for `*args` parameters, we can
812815
// use the tuple itself as the argument type.
813-
let tuple = spec.tuple(db);
814816
let tuple_len = tuple.len();
815817

816818
if tuple_len.minimum() == 0 && tuple_len.maximum().is_none() {
@@ -845,7 +847,7 @@ impl<'db> ClassType<'db> {
845847
// - a zero-length tuple
846848
// - an unspecialized tuple
847849
// - a tuple with no minimum length
848-
if specialization.is_none_or(|spec| spec.tuple(db).len().minimum() == 0) {
850+
if tuple.is_none_or(|tuple| tuple.len().minimum() == 0) {
849851
iterable_parameter =
850852
iterable_parameter.with_default_type(Type::empty_tuple(db));
851853
}
@@ -1061,7 +1063,7 @@ impl<'db> ClassType<'db> {
10611063
match class_literal.known(db)? {
10621064
KnownClass::Tuple => Some(
10631065
specialization
1064-
.map(|spec| Cow::Borrowed(spec.tuple(db)))
1066+
.and_then(|spec| Some(Cow::Borrowed(spec.tuple(db)?)))
10651067
.unwrap_or_else(|| Cow::Owned(TupleSpec::homogeneous(Type::unknown()))),
10661068
),
10671069
KnownClass::VersionInfo => {
@@ -1237,7 +1239,7 @@ impl<'db> ClassLiteral<'db> {
12371239
let class_def_node = scope.node(db).expect_class(&parsed);
12381240
class_def_node.type_params.as_ref().map(|type_params| {
12391241
let index = semantic_index(db, scope.file(db));
1240-
GenericContext::from_type_params(db, index, type_params)
1242+
GenericContext::from_type_params(db, index, type_params, self.known(db))
12411243
})
12421244
}
12431245

@@ -1261,6 +1263,7 @@ impl<'db> ClassLiteral<'db> {
12611263
.iter()
12621264
.copied()
12631265
.filter(|ty| matches!(ty, Type::GenericAlias(_))),
1266+
self.known(db),
12641267
)
12651268
}
12661269

crates/ty_python_semantic/src/types/display.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ impl Display for DisplayRepresentation<'_> {
7979
(ClassType::Generic(alias), Some(KnownClass::Tuple)) => alias
8080
.specialization(self.db)
8181
.tuple(self.db)
82+
.expect("Specialization::tuple() should always return `Some()` for `KnownClass::Tuple`")
8283
.display(self.db)
8384
.fmt(f),
8485
(ClassType::NonGeneric(class), _) => f.write_str(class.name(self.db)),
@@ -386,8 +387,8 @@ pub(crate) struct DisplayGenericAlias<'db> {
386387

387388
impl Display for DisplayGenericAlias<'_> {
388389
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
389-
if self.origin.is_known(self.db, KnownClass::Tuple) {
390-
self.specialization.tuple(self.db).display(self.db).fmt(f)
390+
if let Some(tuple) = self.specialization.tuple(self.db) {
391+
tuple.display(self.db).fmt(f)
391392
} else {
392393
write!(
393394
f,

crates/ty_python_semantic/src/types/function.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ impl<'db> OverloadLiteral<'db> {
338338
let definition = self.definition(db);
339339
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
340340
let index = semantic_index(db, scope.file(db));
341-
GenericContext::from_type_params(db, index, type_params)
341+
GenericContext::from_type_params(db, index, type_params, None)
342342
});
343343

344344
let index = semantic_index(db, scope.file(db));

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ use crate::types::instance::{NominalInstanceType, Protocol, ProtocolInstanceType
1313
use crate::types::signatures::{Parameter, Parameters, Signature};
1414
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
1515
use crate::types::{
16-
KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer, TypeVarBoundOrConstraints,
17-
TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type,
16+
KnownClass, KnownInstanceType, Type, TypeMapping, TypeRelation, TypeTransformer,
17+
TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, TypeVarVariance, UnionType,
18+
binding_type, declaration_type,
1819
};
1920
use crate::{Db, FxOrderSet};
2021

@@ -72,6 +73,8 @@ fn bound_legacy_typevars<'db>(
7273
pub struct GenericContext<'db> {
7374
#[returns(ref)]
7475
pub(crate) variables: FxOrderSet<TypeVarInstance<'db>>,
76+
// If this is the generic context for a class, is it a known class?
77+
known_class: Option<KnownClass>,
7578
}
7679

7780
pub(super) fn walk_generic_context<'db, V: super::visitor::TypeVisitor<'db> + ?Sized>(
@@ -93,12 +96,13 @@ impl<'db> GenericContext<'db> {
9396
db: &'db dyn Db,
9497
index: &'db SemanticIndex<'db>,
9598
type_params_node: &ast::TypeParams,
99+
known_class: Option<KnownClass>,
96100
) -> Self {
97101
let variables: FxOrderSet<_> = type_params_node
98102
.iter()
99103
.filter_map(|type_param| Self::variable_from_type_param(db, index, type_param))
100104
.collect();
101-
Self::new(db, variables)
105+
Self::new(db, variables, known_class)
102106
}
103107

104108
fn variable_from_type_param(
@@ -156,14 +160,15 @@ impl<'db> GenericContext<'db> {
156160
if variables.is_empty() {
157161
return None;
158162
}
159-
Some(Self::new(db, variables))
163+
Some(Self::new(db, variables, None))
160164
}
161165

162166
/// Creates a generic context from the legacy `TypeVar`s that appear in class's base class
163167
/// list.
164168
pub(crate) fn from_base_classes(
165169
db: &'db dyn Db,
166170
bases: impl Iterator<Item = Type<'db>>,
171+
known_class: Option<KnownClass>,
167172
) -> Option<Self> {
168173
let mut variables = FxOrderSet::default();
169174
for base in bases {
@@ -172,7 +177,7 @@ impl<'db> GenericContext<'db> {
172177
if variables.is_empty() {
173178
return None;
174179
}
175-
Some(Self::new(db, variables))
180+
Some(Self::new(db, variables, known_class))
176181
}
177182

178183
pub(crate) fn with_binding_context(
@@ -185,7 +190,7 @@ impl<'db> GenericContext<'db> {
185190
.iter()
186191
.map(|typevar| typevar.with_binding_context(db, binding_context))
187192
.collect();
188-
Self::new(db, variables)
193+
Self::new(db, variables, self.known_class(db))
189194
}
190195

191196
pub(crate) fn len(self, db: &'db dyn Db) -> usize {
@@ -223,7 +228,17 @@ impl<'db> GenericContext<'db> {
223228
}
224229

225230
pub(crate) fn default_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
226-
self.specialize_partial(db, &vec![None; self.variables(db).len()])
231+
let partial = self.specialize_partial(db, &vec![None; self.variables(db).len()]);
232+
if matches!(self.known_class(db), Some(KnownClass::Tuple)) {
233+
Specialization::new(
234+
db,
235+
self,
236+
partial.types(db),
237+
TupleType::homogeneous(db, Type::unknown()),
238+
)
239+
} else {
240+
partial
241+
}
227242
}
228243

229244
pub(crate) fn identity_specialization(self, db: &'db dyn Db) -> Specialization<'db> {
@@ -348,7 +363,7 @@ impl<'db> GenericContext<'db> {
348363
.iter()
349364
.map(|ty| ty.normalized_impl(db, visitor))
350365
.collect();
351-
Self::new(db, variables)
366+
Self::new(db, variables, self.known_class(db))
352367
}
353368
}
354369

@@ -404,18 +419,8 @@ pub(super) fn walk_specialization<'db, V: super::visitor::TypeVisitor<'db> + ?Si
404419

405420
impl<'db> Specialization<'db> {
406421
/// Returns the tuple spec for a specialization of the `tuple` class.
407-
pub(crate) fn tuple(self, db: &'db dyn Db) -> &'db TupleSpec<'db> {
408-
if let Some(tuple) = self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db)) {
409-
return tuple;
410-
}
411-
if let [element_type] = self.types(db) {
412-
if let Some(tuple) = TupleType::new(db, TupleSpec::homogeneous(*element_type)) {
413-
return tuple.tuple(db);
414-
}
415-
}
416-
TupleType::new(db, TupleSpec::homogeneous(Type::unknown()))
417-
.expect("tuple[Unknown, ...] should never contain Never")
418-
.tuple(db)
422+
pub(crate) fn tuple(self, db: &'db dyn Db) -> Option<&'db TupleSpec<'db>> {
423+
self.tuple_inner(db).map(|tuple_type| tuple_type.tuple(db))
419424
}
420425

421426
/// Returns the type that a typevar is mapped to, or None if the typevar isn't part of this

crates/ty_python_semantic/src/types/infer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8958,7 +8958,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
89588958
}
89598959
})
89608960
.collect();
8961-
typevars.map(|typevars| GenericContext::new(self.db(), typevars))
8961+
typevars.map(|typevars| GenericContext::new(self.db(), typevars, None))
89628962
}
89638963

89648964
fn infer_slice_expression(&mut self, slice: &ast::ExprSlice) -> Type<'db> {

0 commit comments

Comments
 (0)