Skip to content

Commit fe50dc8

Browse files
committed
[ty] Use C[T] instead of C[Unknown] for the upper bound of Self
1 parent 48ada2d commit fe50dc8

File tree

6 files changed

+135
-25
lines changed

6 files changed

+135
-25
lines changed

crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,8 @@ def test_seq(x: Sequence[T]) -> Sequence[T]:
411411
return x
412412

413413
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
414-
reveal_type(test_seq(t1)) # revealed: Sequence[int | float | complex | list[int]]
415-
reveal_type(test_seq(t2)) # revealed: Sequence[int | str]
414+
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
415+
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
416416

417417
# TODO: this should be `Sequence[Never]`
418418
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]

crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,8 @@ def test_seq[T](x: Sequence[T]) -> Sequence[T]:
373373
return x
374374

375375
def func8(t1: tuple[complex, list[int]], t2: tuple[int, *tuple[str, ...]], t3: tuple[()]):
376-
reveal_type(test_seq(t1)) # revealed: Sequence[int | float | complex | list[int]]
377-
reveal_type(test_seq(t2)) # revealed: Sequence[int | str]
376+
reveal_type(test_seq(t1)) # revealed: Sequence[Unknown]
377+
reveal_type(test_seq(t2)) # revealed: Sequence[Unknown]
378378

379379
# TODO: this should be `Sequence[Never]`
380380
reveal_type(test_seq(t3)) # revealed: Sequence[Unknown]

crates/ty_python_semantic/resources/mdtest/narrow/isinstance.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,10 @@ a covariant generic, this is equivalent to using the upper bound of the type par
321321
`object`):
322322

323323
```py
324+
from typing import Self
325+
324326
class Covariant[T]:
325-
def get(self) -> T:
327+
def get(self: Self) -> T:
326328
raise NotImplementedError
327329

328330
def _(x: object):
@@ -335,7 +337,7 @@ Similarly, contravariant type parameters use their lower bound of `Never`:
335337

336338
```py
337339
class Contravariant[T]:
338-
def push(self, x: T) -> None: ...
340+
def push(self: Self, x: T) -> None: ...
339341

340342
def _(x: object):
341343
if isinstance(x, Contravariant):
@@ -350,8 +352,8 @@ the type system, so we represent it with the internal `Top[]` special form.
350352

351353
```py
352354
class Invariant[T]:
353-
def push(self, x: T) -> None: ...
354-
def get(self) -> T:
355+
def push(self: Self, x: T) -> None: ...
356+
def get(self: Self) -> T:
355357
raise NotImplementedError
356358

357359
def _(x: object):

crates/ty_python_semantic/src/types.rs

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5661,13 +5661,25 @@ impl<'db> Type<'db> {
56615661
],
56625662
});
56635663
};
5664-
let instance = Type::instance(db, class.unknown_specialization(db));
5664+
5665+
let upper_bound = Type::instance(
5666+
db,
5667+
class.apply_specialization(db, |generic_context| {
5668+
let types = generic_context
5669+
.variables(db)
5670+
.iter()
5671+
.map(|typevar| Type::NonInferableTypeVar(*typevar));
5672+
5673+
generic_context.specialize(db, types.collect())
5674+
}),
5675+
);
5676+
56655677
let class_definition = class.definition(db);
56665678
let typevar = TypeVarInstance::new(
56675679
db,
56685680
ast::name::Name::new_static("Self"),
56695681
Some(class_definition),
5670-
Some(TypeVarBoundOrConstraints::UpperBound(instance).into()),
5682+
Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
56715683
// According to the [spec], we can consider `Self`
56725684
// equivalent to an invariant type variable
56735685
// [spec]: https://typing.python.org/en/latest/spec/generics.html#self
@@ -6009,8 +6021,8 @@ impl<'db> Type<'db> {
60096021
partial.get(db, bound_typevar).unwrap_or(self)
60106022
}
60116023
TypeMapping::MarkTypeVarsInferable(binding_context) => {
6012-
if bound_typevar.binding_context(db) == *binding_context {
6013-
Type::TypeVar(bound_typevar)
6024+
if binding_context.is_none_or(|context| context == bound_typevar.binding_context(db)) {
6025+
Type::TypeVar(bound_typevar.mark_typevars_inferable(db, visitor))
60146026
} else {
60156027
self
60166028
}
@@ -6695,7 +6707,7 @@ pub enum TypeMapping<'a, 'db> {
66956707
/// Replaces occurrences of `typing.Self` with a new `Self` type variable with the given upper bound.
66966708
ReplaceSelf { new_upper_bound: Type<'db> },
66976709
/// Marks the typevars that are bound by a generic class or function as inferable.
6698-
MarkTypeVarsInferable(BindingContext<'db>),
6710+
MarkTypeVarsInferable(Option<BindingContext<'db>>),
66996711
/// Create the top or bottom materialization of a type.
67006712
Materialize(MaterializationKind),
67016713
}
@@ -7636,6 +7648,42 @@ impl<'db> TypeVarInstance<'db> {
76367648
)
76377649
}
76387650

7651+
fn mark_typevars_inferable<'a>(
7652+
self,
7653+
db: &'db dyn Db,
7654+
visitor: &ApplyTypeMappingVisitor<'db>,
7655+
) -> Self {
7656+
let type_mapping = &TypeMapping::MarkTypeVarsInferable(None);
7657+
7658+
Self::new(
7659+
db,
7660+
self.name(db),
7661+
self.definition(db),
7662+
self._bound_or_constraints(db)
7663+
.and_then(|bound_or_constraints| match bound_or_constraints {
7664+
TypeVarBoundOrConstraintsEvaluation::Eager(bound_or_constraints) => Some(
7665+
bound_or_constraints
7666+
.mark_typevars_inferable(db, type_mapping, visitor)
7667+
.into(),
7668+
),
7669+
TypeVarBoundOrConstraintsEvaluation::LazyUpperBound
7670+
| TypeVarBoundOrConstraintsEvaluation::LazyConstraints => {
7671+
Some(bound_or_constraints)
7672+
}
7673+
}),
7674+
self.explicit_variance(db),
7675+
self._default(db).and_then(|default| match default {
7676+
TypeVarDefaultEvaluation::Eager(ty) => {
7677+
Some(ty.apply_type_mapping_impl(db, type_mapping, visitor).into())
7678+
}
7679+
TypeVarDefaultEvaluation::Lazy => self
7680+
.lazy_default(db)
7681+
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor).into()),
7682+
}),
7683+
self.kind(db),
7684+
)
7685+
}
7686+
76397687
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
76407688
let bound_or_constraints = match self.bound_or_constraints(db)? {
76417689
TypeVarBoundOrConstraints::UpperBound(upper_bound) => {
@@ -7866,6 +7914,18 @@ impl<'db> BoundTypeVarInstance<'db> {
78667914
)
78677915
}
78687916

7917+
fn mark_typevars_inferable<'a>(
7918+
self,
7919+
db: &'db dyn Db,
7920+
visitor: &ApplyTypeMappingVisitor<'db>,
7921+
) -> Self {
7922+
Self::new(
7923+
db,
7924+
self.typevar(db).mark_typevars_inferable(db, visitor),
7925+
self.binding_context(db),
7926+
)
7927+
}
7928+
78697929
fn to_instance(self, db: &'db dyn Db) -> Option<Self> {
78707930
Some(Self::new(
78717931
db,
@@ -7971,6 +8031,30 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
79718031
}
79728032
}
79738033
}
8034+
8035+
fn mark_typevars_inferable<'a>(
8036+
self,
8037+
db: &'db dyn Db,
8038+
type_mapping: &TypeMapping<'a, 'db>,
8039+
visitor: &ApplyTypeMappingVisitor<'db>,
8040+
) -> Self {
8041+
match self {
8042+
TypeVarBoundOrConstraints::UpperBound(bound) => TypeVarBoundOrConstraints::UpperBound(
8043+
bound.apply_type_mapping_impl(db, type_mapping, visitor),
8044+
),
8045+
TypeVarBoundOrConstraints::Constraints(constraints) => {
8046+
TypeVarBoundOrConstraints::Constraints(UnionType::new(
8047+
db,
8048+
constraints
8049+
.elements(db)
8050+
.iter()
8051+
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor))
8052+
.collect::<Vec<_>>()
8053+
.into_boxed_slice(),
8054+
))
8055+
}
8056+
}
8057+
}
79748058
}
79758059

79768060
/// Error returned if a type is not awaitable.

crates/ty_python_semantic/src/types/generics.rs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,9 @@ fn is_subtype_in_invariant_position<'db>(
491491
let base_bottom = base_type.bottom_materialization(db);
492492

493493
let is_subtype_of = |derived: Type<'db>, base: Type<'db>| {
494+
if matches!(base, Type::TypeVar(_)) || matches!(derived, Type::TypeVar(_)) {
495+
return ConstraintSet::from(true);
496+
}
494497
derived.has_relation_to_impl(db, base, TypeRelation::Subtyping, visitor)
495498
};
496499
match (derived_materialization, base_materialization) {
@@ -556,16 +559,21 @@ fn has_relation_in_invariant_position<'db>(
556559
// Subtyping between invariant type parameters without a top/bottom materialization involved
557560
// is equivalence
558561
(None, None, TypeRelation::Subtyping) => derived_type.when_equivalent_to(db, *base_type),
559-
(None, None, TypeRelation::Assignability) => derived_type
560-
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
561-
.and(db, || {
562-
base_type.has_relation_to_impl(
563-
db,
564-
*derived_type,
565-
TypeRelation::Assignability,
566-
visitor,
567-
)
568-
}),
562+
(None, None, TypeRelation::Assignability) => {
563+
if matches!(base_type, Type::TypeVar(_)) || matches!(derived_type, Type::TypeVar(_)) {
564+
return ConstraintSet::from(true);
565+
}
566+
derived_type
567+
.has_relation_to_impl(db, *base_type, TypeRelation::Assignability, visitor)
568+
.and(db, || {
569+
base_type.has_relation_to_impl(
570+
db,
571+
*derived_type,
572+
TypeRelation::Assignability,
573+
visitor,
574+
)
575+
})
576+
}
569577
// For gradual types, A <: B (subtyping) is defined as Top[A] <: Bottom[B]
570578
(None, Some(base_mat), TypeRelation::Subtyping) => is_subtype_in_invariant_position(
571579
db,
@@ -858,9 +866,21 @@ impl<'db> Specialization<'db> {
858866
visitor,
859867
),
860868
TypeVarVariance::Covariant => {
869+
if relation.is_assignability() && matches!(self_type, Type::TypeVar(_))
870+
|| matches!(other_type, Type::TypeVar(_))
871+
{
872+
return ConstraintSet::from(true);
873+
}
874+
861875
self_type.has_relation_to_impl(db, *other_type, relation, visitor)
862876
}
863877
TypeVarVariance::Contravariant => {
878+
if relation.is_assignability() && matches!(self_type, Type::TypeVar(_))
879+
|| matches!(other_type, Type::TypeVar(_))
880+
{
881+
return ConstraintSet::from(true);
882+
}
883+
864884
other_type.has_relation_to_impl(db, *self_type, relation, visitor)
865885
}
866886
TypeVarVariance::Bivariant => ConstraintSet::from(true),

crates/ty_python_semantic/src/types/signatures.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,9 @@ impl<'db> Signature<'db> {
367367
let plain_return_ty = definition_expression_type(db, definition, returns.as_ref())
368368
.apply_type_mapping(
369369
db,
370-
&TypeMapping::MarkTypeVarsInferable(BindingContext::Definition(definition)),
370+
&TypeMapping::MarkTypeVarsInferable(Some(BindingContext::Definition(
371+
definition,
372+
))),
371373
);
372374
if function_node.is_async && !is_generator {
373375
KnownClass::CoroutineType
@@ -1549,7 +1551,9 @@ impl<'db> Parameter<'db> {
15491551
annotated_type: parameter.annotation().map(|annotation| {
15501552
definition_expression_type(db, definition, annotation).apply_type_mapping(
15511553
db,
1552-
&TypeMapping::MarkTypeVarsInferable(BindingContext::Definition(definition)),
1554+
&TypeMapping::MarkTypeVarsInferable(Some(BindingContext::Definition(
1555+
definition,
1556+
))),
15531557
)
15541558
}),
15551559
kind,

0 commit comments

Comments
 (0)