[ty] Assume type of self is typing.Self in method calls (#18007)

Part of https://github.com/astral-sh/ty/issues/159

This PR only adjusts the signature of a method so if it has a `self`
argument then that argument will have type of `Typing.Self` even if it's
not specified. If user provides an explicit annotation then Ty will not
override that annotation.

- https://github.com/astral-sh/ty/issues/1131
- https://github.com/astral-sh/ty/issues/1157
- https://github.com/astral-sh/ty/issues/1156
- https://github.com/astral-sh/ty/issues/1173
- https://github.com/astral-sh/ruff/pull/20328
- https://github.com/astral-sh/ty/issues/1163
- https://github.com/astral-sh/ty/issues/1196

Added mdtests.
Also some tests need https://github.com/astral-sh/ruff/pull/18473 to
work completely. So I added a todo for those new cases that I added.

---------

Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
Shaygan Hooshyari 2025-09-22 20:37:20 +02:00 committed by David Peter
parent 24fec9ac1c
commit 3062e71cff
18 changed files with 383 additions and 103 deletions

View file

@ -3030,6 +3030,12 @@ impl Parameters {
.find(|arg| arg.parameter.name.as_str() == name) .find(|arg| arg.parameter.name.as_str() == name)
} }
/// Returns the index of the parameter with the given name
pub fn index(&self, name: &str) -> Option<usize> {
self.iter_non_variadic_params()
.position(|arg| arg.parameter.name.as_str() == name)
}
/// Returns an iterator over all parameters included in this [`Parameters`] node. /// Returns an iterator over all parameters included in this [`Parameters`] node.
pub fn iter(&self) -> ParametersIterator<'_> { pub fn iter(&self) -> ParametersIterator<'_> {
ParametersIterator::new(self) ParametersIterator::new(self)

View file

@ -33,11 +33,6 @@ class Shape:
reveal_type(x) # revealed: Self@nested_func_without_enclosing_binding reveal_type(x) # revealed: Self@nested_func_without_enclosing_binding
inner(self) inner(self)
def implicit_self(self) -> Self:
# TODO: first argument in a method should be considered as "typing.Self"
reveal_type(self) # revealed: Unknown
return self
reveal_type(Shape().nested_type()) # revealed: list[Shape] reveal_type(Shape().nested_type()) # revealed: list[Shape]
reveal_type(Shape().nested_func()) # revealed: Shape reveal_type(Shape().nested_func()) # revealed: Shape
@ -53,6 +48,104 @@ class Outer:
return self return self
``` ```
## Detection of implicit Self
In instance methods, the first parameter (regardless of its name) is assumed to have type
`typing.Self` unless it has an explicit annotation. This does not apply to `@classmethod` and
`@staticmethod`.
```toml
[environment]
python-version = "3.11"
```
```py
from typing import Self
class A:
def implicit_self(self) -> Self:
# TODO: first argument in a method should be considered as "typing.Self"
reveal_type(self) # revealed: Unknown
return self
def foo(self) -> int:
def first_arg_is_not_self(a: int) -> int:
return a
return first_arg_is_not_self(1)
@classmethod
def bar(cls): ...
@staticmethod
def static(x): ...
a = A()
# TODO: Should reveal Self@implicit_self. Requires implicit self in method body(https://github.com/astral-sh/ruff/pull/18473)
reveal_type(a.implicit_self()) # revealed: A
reveal_type(a.implicit_self) # revealed: bound method A.implicit_self() -> A
```
If the method is a class or static method then first argument is not self:
```py
A.bar()
a.static(1)
```
"self" name is not special; any first parameter name is treated as Self.
```py
from typing import Self, Generic, TypeVar
T = TypeVar("T")
class B:
def implicit_this(this) -> Self:
# TODO: Should reveal Self@implicit_this
reveal_type(this) # revealed: Unknown
return this
def ponly(self, /, x: int) -> None:
# TODO: Should reveal Self@ponly
reveal_type(self) # revealed: Unknown
def kwonly(self, *, x: int) -> None:
# TODO: Should reveal Self@kwonly
reveal_type(self) # revealed: Unknown
@property
def name(self) -> str:
# TODO: Should reveal Self@name
reveal_type(self) # revealed: Unknown
return "b"
B.ponly(B(), 1)
B.name
B.kwonly(B(), x=1)
class G(Generic[T]):
def id(self) -> Self:
# TODO: Should reveal Self@id
reveal_type(self) # revealed: Unknown
return self
g = G[int]()
# TODO: Should reveal Self@id Requires implicit self in method body(https://github.com/astral-sh/ruff/pull/18473)
reveal_type(G[int].id(g)) # revealed: G[int]
```
Free functions and nested functions do not use implicit `Self`:
```py
def not_a_method(self):
reveal_type(self) # revealed: Unknown
class C:
def outer(self) -> None:
def inner(self):
reveal_type(self) # revealed: Unknown
```
## typing_extensions ## typing_extensions
```toml ```toml
@ -208,6 +301,53 @@ class MyMetaclass(type):
return super().__new__(cls) return super().__new__(cls)
``` ```
## Explicit Annotation Overrides Implicit `Self`
If the first parameter is explicitly annotated, that annotation takes precedence over the implicit
`Self` treatment.
```toml
[environment]
python-version = "3.11"
```
```py
class Explicit:
# TODO: Should warn the user if self is overriden with a type that is not subtype of the class
def bad(self: int) -> None:
reveal_type(self) # revealed: int
def forward(self: "Explicit") -> None:
reveal_type(self) # revealed: Explicit
e = Explicit()
# error: [invalid-argument-type] "Argument to bound method `bad` is incorrect: Expected `int`, found `Explicit`"
e.bad()
```
## Type of Implicit Self
The assigned type to self argument depends on the method signature. When the method is defined in a
non-generic class and has no other mention of `typing.Self` (for example in return type) then type
of `self` is instance of the class.
```py
from typing import Self
class C:
def f(self) -> Self:
return self
def z(self) -> None: ...
C.z(1) # error: [invalid-argument-type] "Argument to function `z` is incorrect: Expected `C`, found `Literal[1]`"
```
```py
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Argument type `Literal[1]` does not satisfy upper bound `C` of type variable `Self`"
C.f(1)
```
## Binding a method fixes `Self` ## Binding a method fixes `Self`
When a method is bound, any instances of `Self` in its signature are "fixed", since we now know the When a method is bound, any instances of `Self` in its signature are "fixed", since we now know the

View file

@ -69,7 +69,9 @@ reveal_type(bound_method(1)) # revealed: str
When we call the function object itself, we need to pass the `instance` explicitly: When we call the function object itself, we need to pass the `instance` explicitly:
```py ```py
C.f(1) # error: [missing-argument] # error: [invalid-argument-type] "Argument to function `f` is incorrect: Expected `C`, found `Literal[1]`"
# error: [missing-argument]
C.f(1)
reveal_type(C.f(C(), 1)) # revealed: str reveal_type(C.f(C(), 1)) # revealed: str
``` ```

View file

@ -431,6 +431,8 @@ def _(flag: bool):
reveal_type(C7.union_of_class_data_descriptor_and_attribute) # revealed: Literal["data", 2] reveal_type(C7.union_of_class_data_descriptor_and_attribute) # revealed: Literal["data", 2]
C7.union_of_metaclass_attributes = 2 if flag else 1 C7.union_of_metaclass_attributes = 2 if flag else 1
# TODO: https://github.com/astral-sh/ty/issues/1163
# error: [invalid-assignment]
C7.union_of_metaclass_data_descriptor_and_attribute = 2 if flag else 100 C7.union_of_metaclass_data_descriptor_and_attribute = 2 if flag else 100
C7.union_of_class_attributes = 2 if flag else 1 C7.union_of_class_attributes = 2 if flag else 1
C7.union_of_class_data_descriptor_and_attribute = 2 if flag else DataDescriptor() C7.union_of_class_data_descriptor_and_attribute = 2 if flag else DataDescriptor()

View file

@ -562,17 +562,17 @@ class C(Generic[T]):
return u return u
reveal_type(generic_context(C)) # revealed: tuple[T@C] reveal_type(generic_context(C)) # revealed: tuple[T@C]
reveal_type(generic_context(C.method)) # revealed: None reveal_type(generic_context(C.method)) # revealed: tuple[Self@method]
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(C.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
reveal_type(generic_context(C[int])) # revealed: None reveal_type(generic_context(C[int])) # revealed: None
reveal_type(generic_context(C[int].method)) # revealed: None reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method]
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
c: C[int] = C[int]() c: C[int] = C[int]()
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
reveal_type(generic_context(c)) # revealed: None reveal_type(generic_context(c)) # revealed: None
reveal_type(generic_context(c.method)) # revealed: None reveal_type(generic_context(c.method)) # revealed: tuple[Self@method]
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
``` ```
## Specializations propagate ## Specializations propagate

View file

@ -504,17 +504,17 @@ class C[T]:
def cannot_shadow_class_typevar[T](self, t: T): ... def cannot_shadow_class_typevar[T](self, t: T): ...
reveal_type(generic_context(C)) # revealed: tuple[T@C] reveal_type(generic_context(C)) # revealed: tuple[T@C]
reveal_type(generic_context(C.method)) # revealed: None reveal_type(generic_context(C.method)) # revealed: tuple[Self@method]
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(C.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
reveal_type(generic_context(C[int])) # revealed: None reveal_type(generic_context(C[int])) # revealed: None
reveal_type(generic_context(C[int].method)) # revealed: None reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method]
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
c: C[int] = C[int]() c: C[int] = C[int]()
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"] reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
reveal_type(generic_context(c)) # revealed: None reveal_type(generic_context(c)) # revealed: None
reveal_type(generic_context(c.method)) # revealed: None reveal_type(generic_context(c.method)) # revealed: tuple[Self@method]
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method] reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
``` ```
## Specializations propagate ## Specializations propagate

View file

@ -534,6 +534,5 @@ class C:
def _(x: int): def _(x: int):
reveal_type(C().explicit_self(x)) # revealed: tuple[C, int] reveal_type(C().explicit_self(x)) # revealed: tuple[C, int]
# TODO: this should be `tuple[C, int]` as well, once we support implicit `self` reveal_type(C().implicit_self(x)) # revealed: tuple[C, int]
reveal_type(C().implicit_self(x)) # revealed: tuple[Unknown, int]
``` ```

View file

@ -117,6 +117,7 @@ reveal_type(bound_method.__func__) # revealed: def f(self, x: int) -> str
reveal_type(C[int]().f(1)) # revealed: str reveal_type(C[int]().f(1)) # revealed: str
reveal_type(bound_method(1)) # revealed: str reveal_type(bound_method(1)) # revealed: str
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Argument type `Literal[1]` does not satisfy upper bound `C[Unknown]` of type variable `Self`"
C[int].f(1) # error: [missing-argument] C[int].f(1) # error: [missing-argument]
reveal_type(C[int].f(C[int](), 1)) # revealed: str reveal_type(C[int].f(C[int](), 1)) # revealed: str
@ -154,7 +155,7 @@ from ty_extensions import generic_context
legacy.m("string", None) # error: [invalid-argument-type] legacy.m("string", None) # error: [invalid-argument-type]
reveal_type(legacy.m) # revealed: bound method Legacy[int].m[S](x: int, y: S@m) -> S@m reveal_type(legacy.m) # revealed: bound method Legacy[int].m[S](x: int, y: S@m) -> S@m
reveal_type(generic_context(Legacy)) # revealed: tuple[T@Legacy] reveal_type(generic_context(Legacy)) # revealed: tuple[T@Legacy]
reveal_type(generic_context(legacy.m)) # revealed: tuple[S@m] reveal_type(generic_context(legacy.m)) # revealed: tuple[Self@m, S@m]
``` ```
With PEP 695 syntax, it is clearer that the method uses a separate typevar: With PEP 695 syntax, it is clearer that the method uses a separate typevar:

View file

@ -277,9 +277,9 @@ reveal_type(Person._make(("Alice", 42))) # revealed: Unknown
person = Person("Alice", 42) person = Person("Alice", 42)
# error: [invalid-argument-type] "Argument to bound method `_asdict` is incorrect: Expected `NamedTupleFallback`, found `Person`"
reveal_type(person._asdict()) # revealed: dict[str, Any] reveal_type(person._asdict()) # revealed: dict[str, Any]
# TODO: should be `Person` once we support implicit type of `self` reveal_type(person._replace(name="Bob")) # revealed: Person
reveal_type(person._replace(name="Bob")) # revealed: Unknown
``` ```
When accessing them on child classes of generic `NamedTuple`s, the return type is specialized When accessing them on child classes of generic `NamedTuple`s, the return type is specialized
@ -296,8 +296,7 @@ class Box(NamedTuple, Generic[T]):
class IntBox(Box[int]): class IntBox(Box[int]):
pass pass
# TODO: should be `IntBox` once we support the implicit type of `self` reveal_type(IntBox(1)._replace(content=42)) # revealed: IntBox
reveal_type(IntBox(1)._replace(content=42)) # revealed: Unknown
``` ```
## `collections.namedtuple` ## `collections.namedtuple`

View file

@ -363,8 +363,12 @@ class Invariant[T]:
def _(x: object): def _(x: object):
if isinstance(x, Invariant): if isinstance(x, Invariant):
reveal_type(x) # revealed: Top[Invariant[Unknown]] reveal_type(x) # revealed: Top[Invariant[Unknown]]
# error: [invalid-argument-type] "Argument to bound method `get` is incorrect: Expected `Self@get`, found `Top[Invariant[Unknown]]`"
# error: [invalid-argument-type] "Argument to bound method `get` is incorrect: Argument type `Top[Invariant[Unknown]]` does not satisfy upper bound `Bottom[Invariant[Unknown]]` of type variable `Self`"
reveal_type(x.get()) # revealed: object reveal_type(x.get()) # revealed: object
# error: [invalid-argument-type] "Argument to bound method `push` is incorrect: Expected `Never`, found `Literal[42]`" # error: [invalid-argument-type] "Argument to bound method `push` is incorrect: Expected `Never`, found `Literal[42]`"
# error: [invalid-argument-type] "Argument to bound method `push` is incorrect: Expected `Self@push`, found `Top[Invariant[Unknown]]`"
# error: [invalid-argument-type] "Argument to bound method `push` is incorrect: Argument type `Top[Invariant[Unknown]]` does not satisfy upper bound `Bottom[Invariant[Unknown]]` of type variable `Self`"
x.push(42) x.push(42)
``` ```

View file

@ -325,7 +325,7 @@ type A = list[Union["A", str]]
def f(x: A): def f(x: A):
reveal_type(x) # revealed: list[A | str] reveal_type(x) # revealed: list[A | str]
for item in x: for item in x:
reveal_type(item) # revealed: list[A | str] | str reveal_type(item) # revealed: list[Any | str] | str
``` ```
#### With new-style union #### With new-style union
@ -336,7 +336,7 @@ type A = list["A" | str]
def f(x: A): def f(x: A):
reveal_type(x) # revealed: list[A | str] reveal_type(x) # revealed: list[A | str]
for item in x: for item in x:
reveal_type(item) # revealed: list[A | str] | str reveal_type(item) # revealed: list[Any | str] | str
``` ```
#### With Optional #### With Optional
@ -349,7 +349,7 @@ type A = list[Optional[Union["A", str]]]
def f(x: A): def f(x: A):
reveal_type(x) # revealed: list[A | str | None] reveal_type(x) # revealed: list[A | str | None]
for item in x: for item in x:
reveal_type(item) # revealed: list[A | str | None] | str | None reveal_type(item) # revealed: list[Any | str | None] | str | None
``` ```
### Tuple comparison ### Tuple comparison

View file

@ -1948,8 +1948,6 @@ static_assert(is_subtype_of(TypeOf[A.g], Callable[[int], int]))
static_assert(not is_subtype_of(TypeOf[a.f], Callable[[float], int])) static_assert(not is_subtype_of(TypeOf[a.f], Callable[[float], int]))
static_assert(not is_subtype_of(TypeOf[A.g], Callable[[], int])) static_assert(not is_subtype_of(TypeOf[A.g], Callable[[], int]))
# TODO: This assertion should be true
# error: [static-assert-error] "Static assertion error: argument of type `ty_extensions.ConstraintSet[never]` is statically known to be falsy"
static_assert(is_subtype_of(TypeOf[A.f], Callable[[A, int], int])) static_assert(is_subtype_of(TypeOf[A.f], Callable[[A, int], int]))
``` ```

View file

@ -657,15 +657,13 @@ alice: Employee = {"name": "Alice", "employee_id": 1}
eve: Employee = {"name": "Eve"} eve: Employee = {"name": "Eve"}
def combine(p: Person, e: Employee): def combine(p: Person, e: Employee):
# TODO: Should be `Person` once we support the implicit type of self reveal_type(p.copy()) # revealed: Person
reveal_type(p.copy()) # revealed: Unknown reveal_type(e.copy()) # revealed: Employee
# TODO: Should be `Employee` once we support the implicit type of self
reveal_type(e.copy()) # revealed: Unknown
reveal_type(p | p) # revealed: Person reveal_type(p | p) # revealed: Person
reveal_type(e | e) # revealed: Employee reveal_type(e | e) # revealed: Employee
# TODO: Should be `Person` once we support the implicit type of self and subtyping for TypedDicts # TODO: Should be `Person` once we support subtyping for TypedDicts
reveal_type(p | e) # revealed: Employee reveal_type(p | e) # revealed: Employee
``` ```

View file

@ -254,8 +254,7 @@ async def long_running_task():
async def main(): async def main():
async with asyncio.TaskGroup() as tg: async with asyncio.TaskGroup() as tg:
# TODO: should be `TaskGroup` reveal_type(tg) # revealed: TaskGroup
reveal_type(tg) # revealed: Unknown
tg.create_task(long_running_task()) tg.create_task(long_running_task())
``` ```

View file

@ -52,8 +52,8 @@ use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction, DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
}; };
use crate::types::generics::{ use crate::types::generics::{
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context, GenericContext, PartialSpecialization, Specialization, bind_typevar, get_self_type,
walk_partial_specialization, walk_specialization, walk_generic_context, walk_partial_specialization, walk_specialization,
}; };
pub use crate::types::ide_support::{ pub use crate::types::ide_support::{
CallSignatureDetails, Member, MemberWithDefinition, all_members, call_signature_details, CallSignatureDetails, Member, MemberWithDefinition, all_members, call_signature_details,
@ -5703,7 +5703,6 @@ impl<'db> Type<'db> {
.build()), .build()),
SpecialFormType::TypingSelf => { SpecialFormType::TypingSelf => {
let module = parsed_module(db, scope_id.file(db)).load(db);
let index = semantic_index(db, scope_id.file(db)); let index = semantic_index(db, scope_id.file(db));
let Some(class) = nearest_enclosing_class(db, index, scope_id) else { let Some(class) = nearest_enclosing_class(db, index, scope_id) else {
return Err(InvalidTypeExpressionError { return Err(InvalidTypeExpressionError {
@ -5713,42 +5712,8 @@ impl<'db> Type<'db> {
], ],
}); });
}; };
let self_type = get_self_type(db, scope_id, typevar_binding_context, class);
let upper_bound = Type::instance( Ok(self_type.map(Type::NonInferableTypeVar).unwrap_or(*self))
db,
class.apply_specialization(db, |generic_context| {
let types = generic_context
.variables(db)
.iter()
.map(|typevar| Type::NonInferableTypeVar(*typevar));
generic_context.specialize(db, types.collect())
}),
);
let class_definition = class.definition(db);
let typevar = TypeVarInstance::new(
db,
ast::name::Name::new_static("Self"),
Some(class_definition),
Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
// According to the [spec], we can consider `Self`
// equivalent to an invariant type variable
// [spec]: https://typing.python.org/en/latest/spec/generics.html#self
Some(TypeVarVariance::Invariant),
None,
TypeVarKind::TypingSelf,
);
Ok(bind_typevar(
db,
&module,
index,
scope_id.file_scope_id(db),
typevar_binding_context,
typevar,
)
.map(Type::NonInferableTypeVar)
.unwrap_or(*self))
} }
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)), SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError { SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {

View file

@ -1149,11 +1149,13 @@ impl Display for DisplayParameter<'_> {
if let Some(name) = self.param.display_name() { if let Some(name) = self.param.display_name() {
f.write_str(&name)?; f.write_str(&name)?;
if let Some(annotated_type) = self.param.annotated_type() { if let Some(annotated_type) = self.param.annotated_type() {
write!( if !self.param.has_synthetic_annotation() {
f, write!(
": {}", f,
annotated_type.display_with(self.db, self.settings) ": {}",
)?; annotated_type.display_with(self.db, self.settings)
)?;
}
} }
// Default value can only be specified if `name` is given. // Default value can only be specified if `name` is given.
if let Some(default_ty) = self.param.default_type() { if let Some(default_ty) = self.param.default_type() {
@ -1166,7 +1168,9 @@ impl Display for DisplayParameter<'_> {
} else if let Some(ty) = self.param.annotated_type() { } else if let Some(ty) = self.param.annotated_type() {
// This case is specifically for the `Callable` signature where name and default value // This case is specifically for the `Callable` signature where name and default value
// cannot be provided. // cannot be provided.
ty.display_with(self.db, self.settings).fmt(f)?; if !self.param.has_synthetic_annotation() {
ty.display_with(self.db, self.settings).fmt(f)?;
}
} }
Ok(()) Ok(())
} }

View file

@ -3,13 +3,13 @@ use std::borrow::Cow;
use crate::types::constraints::ConstraintSet; use crate::types::constraints::ConstraintSet;
use itertools::Itertools; use itertools::Itertools;
use ruff_db::parsed::ParsedModuleRef; use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast as ast; use ruff_python_ast as ast;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::semantic_index::SemanticIndex;
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind}; use crate::semantic_index::scope::{FileScopeId, NodeWithScopeKind, ScopeId};
use crate::semantic_index::{SemanticIndex, semantic_index};
use crate::types::class::ClassType; use crate::types::class::ClassType;
use crate::types::class_base::ClassBase; use crate::types::class_base::ClassBase;
use crate::types::infer::infer_definition_types; use crate::types::infer::infer_definition_types;
@ -17,10 +17,10 @@ use crate::types::instance::{Protocol, ProtocolInstanceType};
use crate::types::signatures::{Parameter, Parameters, Signature}; use crate::types::signatures::{Parameter, Parameters, Signature};
use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type}; use crate::types::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor,
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor, HasRelationToVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind,
Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind, NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarVariance, UnionType, binding_type, declaration_type, TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
@ -103,6 +103,51 @@ pub(crate) fn bind_typevar<'db>(
}) })
} }
pub(crate) fn get_self_type<'db>(
db: &'db dyn Db,
scope_id: ScopeId,
typevar_binding_context: Option<Definition<'db>>,
class: ClassLiteral<'db>,
) -> Option<BoundTypeVarInstance<'db>> {
// TODO: remove duplication with Type::in_type_expression
let module = parsed_module(db, scope_id.file(db)).load(db);
let index = semantic_index(db, scope_id.file(db));
let upper_bound = Type::instance(
db,
class.apply_specialization(db, |generic_context| {
let types = generic_context
.variables(db)
.iter()
.map(|typevar| Type::NonInferableTypeVar(*typevar));
generic_context.specialize(db, types.collect())
}),
);
let class_definition = class.definition(db);
let typevar = TypeVarInstance::new(
db,
ast::name::Name::new_static("Self"),
Some(class_definition),
Some(TypeVarBoundOrConstraints::UpperBound(upper_bound).into()),
// According to the [spec], we can consider `Self`
// equivalent to an invariant type variable
// [spec]: https://typing.python.org/en/latest/spec/generics.html#self
Some(TypeVarVariance::Invariant),
None,
TypeVarKind::TypingSelf,
);
bind_typevar(
db,
&module,
index,
scope_id.file_scope_id(db),
typevar_binding_context,
typevar,
)
}
/// A list of formal type variables for a generic function, class, or type alias. /// A list of formal type variables for a generic function, class, or type alias.
/// ///
/// TODO: Handle nested generic contexts better, with actual parent links to the lexically /// TODO: Handle nested generic contexts better, with actual parent links to the lexically

View file

@ -15,18 +15,58 @@ use std::{collections::HashMap, slice::Iter};
use itertools::{EitherOrBoth, Itertools}; use itertools::{EitherOrBoth, Itertools};
use smallvec::{SmallVec, smallvec_inline}; use smallvec::{SmallVec, smallvec_inline};
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; use super::{
DynamicType, Type, TypeVarVariance, definition_expression_type, infer_definition_types,
semantic_index,
};
use crate::semantic_index::definition::Definition; use crate::semantic_index::definition::Definition;
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::function::FunctionType;
use crate::types::generics::{GenericContext, get_self_type, walk_generic_context};
use crate::types::infer::nearest_enclosing_class;
use crate::types::{ use crate::types::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType,
HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
TypeMapping, TypeRelation, VarianceInferable, todo_type, MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation, TypeVarKind,
VarianceInferable, todo_type,
}; };
use crate::{Db, FxOrderSet}; use crate::{Db, FxOrderSet};
use ruff_python_ast::{self as ast, name::Name}; use ruff_python_ast::{self as ast, name::Name};
#[derive(Clone, Copy, Debug)]
struct MethodInformation<'db> {
method_type: FunctionType<'db>,
class_type: ClassType<'db>,
}
fn infer_method_information<'db>(
db: &'db dyn Db,
definition: Definition<'db>,
) -> Option<MethodInformation<'db>> {
let class_scope_id = definition.scope(db);
let file = class_scope_id.file(db);
let index = semantic_index(db, file);
let class_scope = index.scope(class_scope_id.file_scope_id(db));
let class_node = class_scope.node().as_class()?;
let method_type = infer_definition_types(db, definition)
.declaration_type(definition)
.inner_type()
.into_function_literal()?;
let class_def = index.expect_single_definition(class_node);
let class_literal = infer_definition_types(db, class_def)
.declaration_type(class_def)
.inner_type();
let class_type = class_literal.to_class_type(db)?;
Some(MethodInformation {
method_type,
class_type,
})
}
/// The signature of a single callable. If the callable is overloaded, there is a separate /// The signature of a single callable. If the callable is overloaded, there is a separate
/// [`Signature`] for each overload. /// [`Signature`] for each overload.
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
@ -1219,17 +1259,77 @@ impl<'db> Parameters<'db> {
.map(pos_only_param), .map(pos_only_param),
); );
} }
let method_info = infer_method_information(db, definition);
let is_classmethod = method_info.is_some_and(|f| f.method_type.is_classmethod(db));
let is_staticmethod = method_info.is_some_and(|f| f.method_type.is_staticmethod(db));
let positional_or_keyword = pos_or_keyword_iter.map(|arg| { let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
Parameter::from_node_and_kind( if let Some(MethodInformation {
db, method_type: method,
definition, class_type: class,
&arg.parameter, }) = method_info
ParameterKind::PositionalOrKeyword { {
name: arg.parameter.name.id.clone(), if !is_staticmethod
default_type: default_type(arg), && !is_classmethod
}, && arg.parameter.annotation().is_none()
) && parameters.index(arg.name().id()) == Some(0)
{
let method_has_self_in_generic_context =
method.signature(db).overloads.iter().any(|s| {
if let Some(context) = s.generic_context {
context
.variables(db)
.iter()
.any(|v| v.typevar(db).kind(db) == TypeVarKind::TypingSelf);
true
} else {
false
}
});
let implicit_annotation = if !method_has_self_in_generic_context
&& class.is_not_generic()
{
Type::instance(db, class)
} else {
let scope_id = definition.scope(db);
let typevar_binding_context = Some(definition);
let index = semantic_index(db, scope_id.file(db));
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
Type::TypeVar(
get_self_type(db, scope_id, typevar_binding_context, class).unwrap(),
)
};
Parameter {
annotated_type: Some(implicit_annotation),
synthetic_annotation: true,
kind: ParameterKind::PositionalOrKeyword {
name: arg.parameter.name.id.clone(),
default_type: default_type(arg),
},
form: ParameterForm::Value,
}
} else {
Parameter::from_node_and_kind(
db,
definition,
&arg.parameter,
ParameterKind::PositionalOrKeyword {
name: arg.parameter.name.id.clone(),
default_type: default_type(arg),
},
)
}
} else {
Parameter::from_node_and_kind(
db,
definition,
&arg.parameter,
ParameterKind::PositionalOrKeyword {
name: arg.parameter.name.id.clone(),
default_type: default_type(arg),
},
)
}
}); });
let variadic = vararg.as_ref().map(|arg| { let variadic = vararg.as_ref().map(|arg| {
@ -1402,6 +1502,10 @@ pub(crate) struct Parameter<'db> {
/// Annotated type of the parameter. /// Annotated type of the parameter.
annotated_type: Option<Type<'db>>, annotated_type: Option<Type<'db>>,
/// If the type of parameter was inferred e.g. the first argument of a method has type
/// `typing.Self`.
synthetic_annotation: bool,
kind: ParameterKind<'db>, kind: ParameterKind<'db>,
pub(crate) form: ParameterForm, pub(crate) form: ParameterForm,
} }
@ -1410,6 +1514,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn positional_only(name: Option<Name>) -> Self { pub(crate) fn positional_only(name: Option<Name>) -> Self {
Self { Self {
annotated_type: None, annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::PositionalOnly { kind: ParameterKind::PositionalOnly {
name, name,
default_type: None, default_type: None,
@ -1421,6 +1526,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn positional_or_keyword(name: Name) -> Self { pub(crate) fn positional_or_keyword(name: Name) -> Self {
Self { Self {
annotated_type: None, annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::PositionalOrKeyword { kind: ParameterKind::PositionalOrKeyword {
name, name,
default_type: None, default_type: None,
@ -1432,6 +1538,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn variadic(name: Name) -> Self { pub(crate) fn variadic(name: Name) -> Self {
Self { Self {
annotated_type: None, annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::Variadic { name }, kind: ParameterKind::Variadic { name },
form: ParameterForm::Value, form: ParameterForm::Value,
} }
@ -1440,6 +1547,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn keyword_only(name: Name) -> Self { pub(crate) fn keyword_only(name: Name) -> Self {
Self { Self {
annotated_type: None, annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::KeywordOnly { kind: ParameterKind::KeywordOnly {
name, name,
default_type: None, default_type: None,
@ -1451,6 +1559,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn keyword_variadic(name: Name) -> Self { pub(crate) fn keyword_variadic(name: Name) -> Self {
Self { Self {
annotated_type: None, annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::KeywordVariadic { name }, kind: ParameterKind::KeywordVariadic { name },
form: ParameterForm::Value, form: ParameterForm::Value,
} }
@ -1489,6 +1598,7 @@ impl<'db> Parameter<'db> {
.annotated_type .annotated_type
.map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)), .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)),
kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor), kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor),
synthetic_annotation: self.synthetic_annotation,
form: self.form, form: self.form,
} }
} }
@ -1504,6 +1614,7 @@ impl<'db> Parameter<'db> {
) -> Self { ) -> Self {
let Parameter { let Parameter {
annotated_type, annotated_type,
synthetic_annotation,
kind, kind,
form, form,
} = self; } = self;
@ -1547,6 +1658,7 @@ impl<'db> Parameter<'db> {
Self { Self {
annotated_type: Some(annotated_type), annotated_type: Some(annotated_type),
synthetic_annotation: *synthetic_annotation,
kind, kind,
form: *form, form: *form,
} }
@ -1569,6 +1681,7 @@ impl<'db> Parameter<'db> {
}), }),
kind, kind,
form: ParameterForm::Value, form: ParameterForm::Value,
synthetic_annotation: false,
} }
} }
@ -1623,6 +1736,11 @@ impl<'db> Parameter<'db> {
&self.kind &self.kind
} }
/// Whether the type of the parameter was inferred.
pub(crate) fn has_synthetic_annotation(&self) -> bool {
self.synthetic_annotation
}
/// Name of the parameter (if it has one). /// Name of the parameter (if it has one).
pub(crate) fn name(&self) -> Option<&ast::name::Name> { pub(crate) fn name(&self) -> Option<&ast::name::Name> {
match &self.kind { match &self.kind {