[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)
}
/// 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.
pub fn iter(&self) -> ParametersIterator<'_> {
ParametersIterator::new(self)

View file

@ -33,11 +33,6 @@ class Shape:
reveal_type(x) # revealed: Self@nested_func_without_enclosing_binding
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_func()) # revealed: Shape
@ -53,6 +48,104 @@ class Outer:
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
```toml
@ -208,6 +301,53 @@ class MyMetaclass(type):
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`
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:
```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
```

View file

@ -431,6 +431,8 @@ def _(flag: bool):
reveal_type(C7.union_of_class_data_descriptor_and_attribute) # revealed: Literal["data", 2]
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_class_attributes = 2 if flag else 1
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
reveal_type(generic_context(C)) # revealed: tuple[T@C]
reveal_type(generic_context(C.method)) # revealed: None
reveal_type(generic_context(C.generic_method)) # revealed: tuple[U@generic_method]
reveal_type(generic_context(C.method)) # revealed: tuple[Self@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].method)) # revealed: None
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[U@generic_method]
reveal_type(generic_context(C[int].method)) # revealed: tuple[Self@method]
reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
c: C[int] = C[int]()
reveal_type(c.generic_method(1, "string")) # revealed: Literal["string"]
reveal_type(generic_context(c)) # revealed: None
reveal_type(generic_context(c.method)) # revealed: None
reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method]
reveal_type(generic_context(c.method)) # revealed: tuple[Self@method]
reveal_type(generic_context(c.generic_method)) # revealed: tuple[Self@generic_method, U@generic_method]
```
## Specializations propagate

View file

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

View file

@ -534,6 +534,5 @@ class C:
def _(x: 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[Unknown, int]
reveal_type(C().implicit_self(x)) # revealed: tuple[C, 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(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]
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]
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.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:

View file

@ -277,9 +277,9 @@ reveal_type(Person._make(("Alice", 42))) # revealed: Unknown
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]
# TODO: should be `Person` once we support implicit type of `self`
reveal_type(person._replace(name="Bob")) # revealed: Unknown
reveal_type(person._replace(name="Bob")) # revealed: Person
```
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]):
pass
# TODO: should be `IntBox` once we support the implicit type of `self`
reveal_type(IntBox(1)._replace(content=42)) # revealed: Unknown
reveal_type(IntBox(1)._replace(content=42)) # revealed: IntBox
```
## `collections.namedtuple`

View file

@ -363,8 +363,12 @@ class Invariant[T]:
def _(x: object):
if isinstance(x, Invariant):
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
# 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)
```

View file

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

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.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]))
```

View file

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

View file

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

View file

@ -52,8 +52,8 @@ use crate::types::function::{
DataclassTransformerParams, FunctionSpans, FunctionType, KnownFunction,
};
use crate::types::generics::{
GenericContext, PartialSpecialization, Specialization, bind_typevar, walk_generic_context,
walk_partial_specialization, walk_specialization,
GenericContext, PartialSpecialization, Specialization, bind_typevar, get_self_type,
walk_generic_context, walk_partial_specialization, walk_specialization,
};
pub use crate::types::ide_support::{
CallSignatureDetails, Member, MemberWithDefinition, all_members, call_signature_details,
@ -5703,7 +5703,6 @@ impl<'db> Type<'db> {
.build()),
SpecialFormType::TypingSelf => {
let module = parsed_module(db, scope_id.file(db)).load(db);
let index = semantic_index(db, scope_id.file(db));
let Some(class) = nearest_enclosing_class(db, index, scope_id) else {
return Err(InvalidTypeExpressionError {
@ -5713,42 +5712,8 @@ impl<'db> Type<'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,
);
Ok(bind_typevar(
db,
&module,
index,
scope_id.file_scope_id(db),
typevar_binding_context,
typevar,
)
.map(Type::NonInferableTypeVar)
.unwrap_or(*self))
let self_type = get_self_type(db, scope_id, typevar_binding_context, class);
Ok(self_type.map(Type::NonInferableTypeVar).unwrap_or(*self))
}
SpecialFormType::TypeAlias => Ok(Type::Dynamic(DynamicType::TodoTypeAlias)),
SpecialFormType::TypedDict => Err(InvalidTypeExpressionError {

View file

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

View file

@ -3,13 +3,13 @@ use std::borrow::Cow;
use crate::types::constraints::ConstraintSet;
use itertools::Itertools;
use ruff_db::parsed::ParsedModuleRef;
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast as ast;
use rustc_hash::FxHashMap;
use crate::semantic_index::SemanticIndex;
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_base::ClassBase;
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::tuple::{TupleSpec, TupleType, walk_tuple_type};
use crate::types::{
ApplyTypeMappingVisitor, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind, NormalizedVisitor,
Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
TypeVarVariance, UnionType, binding_type, declaration_type,
ApplyTypeMappingVisitor, BoundTypeVarInstance, ClassLiteral, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, MaterializationKind,
NormalizedVisitor, Type, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance,
TypeVarKind, TypeVarVariance, UnionType, binding_type, declaration_type,
};
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.
///
/// 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 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::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::{
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor,
TypeMapping, TypeRelation, VarianceInferable, todo_type,
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, ClassType,
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsEquivalentVisitor, KnownClass,
MaterializationKind, NormalizedVisitor, TypeMapping, TypeRelation, TypeVarKind,
VarianceInferable, todo_type,
};
use crate::{Db, FxOrderSet};
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
/// [`Signature`] for each overload.
#[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)]
@ -1219,8 +1259,56 @@ impl<'db> Parameters<'db> {
.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| {
if let Some(MethodInformation {
method_type: method,
class_type: class,
}) = method_info
{
if !is_staticmethod
&& !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,
@ -1230,6 +1318,18 @@ impl<'db> Parameters<'db> {
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| {
@ -1402,6 +1502,10 @@ pub(crate) struct Parameter<'db> {
/// Annotated type of the parameter.
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>,
pub(crate) form: ParameterForm,
}
@ -1410,6 +1514,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn positional_only(name: Option<Name>) -> Self {
Self {
annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::PositionalOnly {
name,
default_type: None,
@ -1421,6 +1526,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn positional_or_keyword(name: Name) -> Self {
Self {
annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::PositionalOrKeyword {
name,
default_type: None,
@ -1432,6 +1538,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn variadic(name: Name) -> Self {
Self {
annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::Variadic { name },
form: ParameterForm::Value,
}
@ -1440,6 +1547,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn keyword_only(name: Name) -> Self {
Self {
annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::KeywordOnly {
name,
default_type: None,
@ -1451,6 +1559,7 @@ impl<'db> Parameter<'db> {
pub(crate) fn keyword_variadic(name: Name) -> Self {
Self {
annotated_type: None,
synthetic_annotation: false,
kind: ParameterKind::KeywordVariadic { name },
form: ParameterForm::Value,
}
@ -1489,6 +1598,7 @@ impl<'db> Parameter<'db> {
.annotated_type
.map(|ty| ty.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,
}
}
@ -1504,6 +1614,7 @@ impl<'db> Parameter<'db> {
) -> Self {
let Parameter {
annotated_type,
synthetic_annotation,
kind,
form,
} = self;
@ -1547,6 +1658,7 @@ impl<'db> Parameter<'db> {
Self {
annotated_type: Some(annotated_type),
synthetic_annotation: *synthetic_annotation,
kind,
form: *form,
}
@ -1569,6 +1681,7 @@ impl<'db> Parameter<'db> {
}),
kind,
form: ParameterForm::Value,
synthetic_annotation: false,
}
}
@ -1623,6 +1736,11 @@ impl<'db> Parameter<'db> {
&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).
pub(crate) fn name(&self) -> Option<&ast::name::Name> {
match &self.kind {