mirror of https://github.com/astral-sh/ruff
[ty] Don't confuse multiple occurrences of `typing.Self` when binding bound methods (#21754)
In the following example, there are two occurrences of `typing.Self`,
one for `Foo.foo` and one for `Bar.bar`:
```py
from typing import Self, reveal_type
class Foo[T]:
def foo(self: Self) -> T:
raise NotImplementedError
class Bar:
def bar(self: Self, x: Foo[Self]):
# SHOULD BE: bound method Foo[Self@bar].foo() -> Self@bar
# revealed: bound method Foo[Self@bar].foo() -> Foo[Self@bar]
reveal_type(x.foo)
def f[U: Bar](x: Foo[U]):
# revealed: bound method Foo[U@f].foo() -> U@f
reveal_type(x.foo)
```
When accessing a bound method, we replace any occurrences of `Self` with
the bound `self` type.
We were doing this correctly for the second reveal. We would first apply
the specialization, getting `(self: Self@foo) -> U@F` as the signature
of `x.foo`. We would then bind the `self` parameter, substituting
`Self@foo` with `Foo[U@F]` as part of that. The return type was already
specialized to `U@F`, so that substitution had no further affect on the
type that we revealed.
In the first reveal, we would follow the same process, but we confused
the two occurrences of `Self`. We would first apply the specialization,
getting `(self: Self@foo) -> Self@bar` as the method signature. We would
then try to bind the `self` parameter, substituting `Self@foo` with
`Foo[Self@bar]`. However, because we didn't distinguish the two separate
`Self`s, and applied the substitution to the return type as well as to
the `self` parameter.
The fix is to track which particular `Self` we're trying to substitute
when applying the type mapping.
Fixes https://github.com/astral-sh/ty/issues/1713
This commit is contained in:
parent
0d2792517d
commit
508c0a0861
|
|
@ -232,6 +232,32 @@ class C:
|
|||
reveal_type(not_a_method) # revealed: def not_a_method(self) -> Unknown
|
||||
```
|
||||
|
||||
## Different occurrences of `Self` represent different types
|
||||
|
||||
Here, both `Foo.foo` and `Bar.bar` use `Self`. When accessing a bound method, we replace any
|
||||
occurrences of `Self` with the bound `self` type. In this example, when we access `x.foo`, we only
|
||||
want to substitute the occurrences of `Self` in `Foo.foo` — that is, occurrences of `Self@foo`. The
|
||||
fact that `x` is an instance of `Foo[Self@bar]` (a completely different `Self` type) should not
|
||||
affect that subtitution. If we blindly substitute all occurrences of `Self`, we would get
|
||||
`Foo[Self@bar]` as the return type of the bound method.
|
||||
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
class Foo[T]:
|
||||
def foo(self: Self) -> T:
|
||||
raise NotImplementedError
|
||||
|
||||
class Bar:
|
||||
def bar(self: Self, x: Foo[Self]):
|
||||
# revealed: bound method Foo[Self@bar].foo() -> Self@bar
|
||||
reveal_type(x.foo)
|
||||
|
||||
def f[U: Bar](x: Foo[U]):
|
||||
# revealed: bound method Foo[U@f].foo() -> U@f
|
||||
reveal_type(x.foo)
|
||||
```
|
||||
|
||||
## typing_extensions
|
||||
|
||||
```toml
|
||||
|
|
|
|||
|
|
@ -7550,10 +7550,9 @@ impl<'db> Type<'db> {
|
|||
// If we are binding `typing.Self`, and this type is what we are binding `Self` to, return
|
||||
// early. This is not just an optimization, it also prevents us from infinitely expanding
|
||||
// the type, if it's something that can contain a `Self` reference.
|
||||
if let TypeMapping::BindSelf(self_type) = type_mapping
|
||||
&& self == *self_type
|
||||
{
|
||||
return self;
|
||||
match type_mapping {
|
||||
TypeMapping::BindSelf { self_type, .. } if self == *self_type => return self,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match self {
|
||||
|
|
@ -7568,7 +7567,7 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::Specialization(_) |
|
||||
TypeMapping::PartialSpecialization(_) |
|
||||
TypeMapping::PromoteLiterals(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::BindSelf { .. } |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::Materialize(_) |
|
||||
TypeMapping::ReplaceParameterDefaults |
|
||||
|
|
@ -7744,7 +7743,7 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::Specialization(_) |
|
||||
TypeMapping::PartialSpecialization(_) |
|
||||
TypeMapping::BindLegacyTypevars(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::BindSelf { .. } |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::Materialize(_) |
|
||||
TypeMapping::ReplaceParameterDefaults |
|
||||
|
|
@ -7757,7 +7756,7 @@ impl<'db> Type<'db> {
|
|||
TypeMapping::Specialization(_) |
|
||||
TypeMapping::PartialSpecialization(_) |
|
||||
TypeMapping::BindLegacyTypevars(_) |
|
||||
TypeMapping::BindSelf(_) |
|
||||
TypeMapping::BindSelf { .. } |
|
||||
TypeMapping::ReplaceSelf { .. } |
|
||||
TypeMapping::PromoteLiterals(_) |
|
||||
TypeMapping::ReplaceParameterDefaults |
|
||||
|
|
@ -8421,7 +8420,10 @@ pub enum TypeMapping<'a, 'db> {
|
|||
/// being used in.
|
||||
BindLegacyTypevars(BindingContext<'db>),
|
||||
/// Binds any `typing.Self` typevar with a particular `self` class.
|
||||
BindSelf(Type<'db>),
|
||||
BindSelf {
|
||||
self_type: Type<'db>,
|
||||
binding_context: Option<BindingContext<'db>>,
|
||||
},
|
||||
/// Replaces occurrences of `typing.Self` with a new `Self` type variable with the given upper bound.
|
||||
ReplaceSelf { new_upper_bound: Type<'db> },
|
||||
/// Create the top or bottom materialization of a type.
|
||||
|
|
@ -8449,7 +8451,7 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
| TypeMapping::Materialize(_)
|
||||
| TypeMapping::ReplaceParameterDefaults
|
||||
| TypeMapping::EagerExpansion => context,
|
||||
TypeMapping::BindSelf(_) => GenericContext::from_typevar_instances(
|
||||
TypeMapping::BindSelf { .. } => GenericContext::from_typevar_instances(
|
||||
db,
|
||||
context
|
||||
.variables(db)
|
||||
|
|
@ -8482,7 +8484,7 @@ impl<'db> TypeMapping<'_, 'db> {
|
|||
TypeMapping::Specialization(_)
|
||||
| TypeMapping::PartialSpecialization(_)
|
||||
| TypeMapping::BindLegacyTypevars(_)
|
||||
| TypeMapping::BindSelf(_)
|
||||
| TypeMapping::BindSelf { .. }
|
||||
| TypeMapping::ReplaceSelf { .. }
|
||||
| TypeMapping::ReplaceParameterDefaults
|
||||
| TypeMapping::EagerExpansion => self.clone(),
|
||||
|
|
@ -9837,8 +9839,13 @@ impl<'db> BoundTypeVarInstance<'db> {
|
|||
TypeMapping::PartialSpecialization(partial) => {
|
||||
partial.get(db, self).unwrap_or(Type::TypeVar(self))
|
||||
}
|
||||
TypeMapping::BindSelf(self_type) => {
|
||||
if self.typevar(db).is_self(db) {
|
||||
TypeMapping::BindSelf {
|
||||
self_type,
|
||||
binding_context,
|
||||
} => {
|
||||
if self.typevar(db).is_self(db)
|
||||
&& binding_context.is_none_or(|context| self.binding_context(db) == context)
|
||||
{
|
||||
*self_type
|
||||
} else {
|
||||
Type::TypeVar(self)
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ use crate::types::generics::{
|
|||
};
|
||||
use crate::types::infer::nearest_enclosing_class;
|
||||
use crate::types::{
|
||||
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor,
|
||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind,
|
||||
NormalizedVisitor, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassLiteral,
|
||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
||||
KnownClass, MaterializationKind, NormalizedVisitor, TypeContext, TypeMapping, TypeRelation,
|
||||
VarianceInferable, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
|
@ -667,19 +668,18 @@ impl<'db> Signature<'db> {
|
|||
let mut parameters = Parameters::new(db, parameters);
|
||||
let mut return_ty = self.return_ty;
|
||||
if let Some(self_type) = self_type {
|
||||
let self_mapping = TypeMapping::BindSelf {
|
||||
self_type,
|
||||
binding_context: self.definition.map(BindingContext::Definition),
|
||||
};
|
||||
parameters = parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
&self_mapping,
|
||||
TypeContext::default(),
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
);
|
||||
return_ty = return_ty.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
return_ty = return_ty
|
||||
.map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default()));
|
||||
}
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
|
|
@ -690,19 +690,19 @@ impl<'db> Signature<'db> {
|
|||
}
|
||||
|
||||
pub(crate) fn apply_self(&self, db: &'db dyn Db, self_type: Type<'db>) -> Self {
|
||||
let self_mapping = TypeMapping::BindSelf {
|
||||
self_type,
|
||||
binding_context: self.definition.map(BindingContext::Definition),
|
||||
};
|
||||
let parameters = self.parameters.apply_type_mapping_impl(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
&self_mapping,
|
||||
TypeContext::default(),
|
||||
&ApplyTypeMappingVisitor::default(),
|
||||
);
|
||||
let return_ty = self.return_ty.map(|ty| {
|
||||
ty.apply_type_mapping(
|
||||
db,
|
||||
&TypeMapping::BindSelf(self_type),
|
||||
TypeContext::default(),
|
||||
)
|
||||
});
|
||||
let return_ty = self
|
||||
.return_ty
|
||||
.map(|ty| ty.apply_type_mapping(db, &self_mapping, TypeContext::default()));
|
||||
Self {
|
||||
generic_context: self.generic_context,
|
||||
definition: self.definition,
|
||||
|
|
|
|||
Loading…
Reference in New Issue