Compare commits
16 Commits
ag/auto-im
...
david/sign
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a3101b0be | ||
|
|
0f371f6efd | ||
|
|
cbaac56cf1 | ||
|
|
99ecb657f6 | ||
|
|
31566d67cb | ||
|
|
f0b0d2ef87 | ||
|
|
4c7f7d199b | ||
|
|
a66add41be | ||
|
|
4064fa28fc | ||
|
|
a51982bac6 | ||
|
|
a27013bdd3 | ||
|
|
c24236cc73 | ||
|
|
0b19caedec | ||
|
|
178f48fc0b | ||
|
|
f34b6d8245 | ||
|
|
8dd183e55c |
@@ -232,7 +232,7 @@ static STATIC_FRAME: std::sync::LazyLock<Benchmark<'static>> = std::sync::LazyLo
|
||||
max_dep_date: "2025-08-09",
|
||||
python_version: PythonVersion::PY311,
|
||||
},
|
||||
500,
|
||||
600,
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.11"
|
||||
python-version = "3.13"
|
||||
```
|
||||
|
||||
`Self` is treated as if it were a `TypeVar` bound to the class it's being used on.
|
||||
@@ -30,16 +30,9 @@ class Shape:
|
||||
|
||||
def nested_func_without_enclosing_binding(self):
|
||||
def inner(x: Self):
|
||||
# TODO: revealed: Self@nested_func_without_enclosing_binding
|
||||
# (The outer method binds an implicit `Self`)
|
||||
reveal_type(x) # revealed: Self@inner
|
||||
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
|
||||
|
||||
@@ -55,6 +48,102 @@ 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()
|
||||
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]()
|
||||
|
||||
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
|
||||
@@ -147,6 +236,23 @@ class Shape:
|
||||
return self
|
||||
```
|
||||
|
||||
## `Self` for classes with a default value for their generic parameter
|
||||
|
||||
This is a regression test for <https://github.com/astral-sh/ty/issues/1156>.
|
||||
|
||||
```py
|
||||
from typing import Self
|
||||
|
||||
class Container[T = bytes]:
|
||||
def __init__(self: Self, data: T | None = None) -> None:
|
||||
self.data = data
|
||||
|
||||
reveal_type(Container()) # revealed: Container[bytes]
|
||||
reveal_type(Container(1)) # revealed: Container[int]
|
||||
reveal_type(Container("a")) # revealed: Container[str]
|
||||
reveal_type(Container(b"a")) # revealed: Container[bytes]
|
||||
```
|
||||
|
||||
## Invalid Usage
|
||||
|
||||
`Self` cannot be used in the signature of a function or variable.
|
||||
@@ -193,6 +299,30 @@ 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()
|
||||
```
|
||||
|
||||
## Binding a method fixes `Self`
|
||||
|
||||
When a method is bound, any instances of `Self` in its signature are "fixed", since we now know the
|
||||
|
||||
@@ -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: Argument type `Literal[1]` does not satisfy upper bound of type variable `Self`"
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
C.f(1)
|
||||
|
||||
reveal_type(C.f(C(), 1)) # revealed: str
|
||||
```
|
||||
|
||||
@@ -431,6 +431,7 @@ 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
|
||||
# error: [invalid-assignment] "Invalid assignment to data descriptor attribute `union_of_metaclass_data_descriptor_and_attribute` on type `<class 'C7'>` with custom `__set__` method"
|
||||
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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -512,7 +512,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]
|
||||
```
|
||||
|
||||
@@ -117,7 +117,9 @@ 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
|
||||
|
||||
C[int].f(1) # error: [missing-argument]
|
||||
# error: [missing-argument] "No argument provided for required parameter `x` of function `f`"
|
||||
# error: [invalid-argument-type] "Argument to function `f` is incorrect: Argument type `Literal[1]` does not satisfy upper bound of type variable `Self`"
|
||||
C[int].f(1)
|
||||
reveal_type(C[int].f(C[int](), 1)) # revealed: str
|
||||
|
||||
class D[U](C[U]):
|
||||
@@ -154,7 +156,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:
|
||||
|
||||
@@ -277,8 +277,11 @@ reveal_type(Person._make(("Alice", 42))) # revealed: Unknown
|
||||
|
||||
person = Person("Alice", 42)
|
||||
|
||||
# TODO: should not be an error
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(person._asdict()) # revealed: dict[str, Any]
|
||||
# TODO: should be `Person` once we support `Self`
|
||||
# TODO: should be `Person` once we support `Self`, should not be an error
|
||||
# error: [invalid-argument-type]
|
||||
reveal_type(person._replace(name="Bob")) # revealed: Unknown
|
||||
```
|
||||
|
||||
|
||||
@@ -357,8 +357,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 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 of type variable `Self`"
|
||||
x.push(42)
|
||||
```
|
||||
|
||||
|
||||
@@ -313,7 +313,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
|
||||
@@ -324,7 +324,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
|
||||
@@ -337,5 +337,5 @@ 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
|
||||
```
|
||||
|
||||
@@ -74,7 +74,7 @@ error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
10 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
11 | # error: [invalid-argument-type]
|
||||
12 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound of type variable `T`
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound of type variable `T` (int)
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:4:1
|
||||
|
||||
@@ -89,7 +89,7 @@ error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
11 | reveal_type(f(None)) # revealed: None
|
||||
12 | # error: [invalid-argument-type]
|
||||
13 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints of type variable `T`
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints of type variable `T` (int, None)
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:4:1
|
||||
|
||||
@@ -71,7 +71,7 @@ error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
7 | reveal_type(f(True)) # revealed: Literal[True]
|
||||
8 | # error: [invalid-argument-type]
|
||||
9 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound of type variable `T`
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy upper bound of type variable `T` (int)
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
||||
@@ -86,7 +86,7 @@ error[invalid-argument-type]: Argument to function `f` is incorrect
|
||||
8 | reveal_type(f(None)) # revealed: None
|
||||
9 | # error: [invalid-argument-type]
|
||||
10 | reveal_type(f("string")) # revealed: Unknown
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints of type variable `T`
|
||||
| ^^^^^^^^ Argument type `Literal["string"]` does not satisfy constraints of type variable `T` (int, None)
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:3:7
|
||||
|
||||
@@ -188,7 +188,7 @@ error[invalid-argument-type]: Argument to function `f4` is incorrect
|
||||
58 | # error: [call-non-callable] "Object of type `Literal[5]` is not callable"
|
||||
59 | # error: [call-non-callable] "Object of type `PossiblyNotCallable` is not callable (possibly unbound `__call__` method)"
|
||||
60 | x = f(3)
|
||||
| ^ Argument type `Literal[3]` does not satisfy upper bound of type variable `T`
|
||||
| ^ Argument type `Literal[3]` does not satisfy upper bound of type variable `T` (str)
|
||||
|
|
||||
info: Type variable defined here
|
||||
--> src/mdtest_snippet.py:13:8
|
||||
|
||||
@@ -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())
|
||||
```
|
||||
|
||||
@@ -5708,9 +5708,7 @@ impl<'db> Type<'db> {
|
||||
],
|
||||
});
|
||||
};
|
||||
let instance = Type::ClassLiteral(class).to_instance(db).expect(
|
||||
"nearest_enclosing_class must return type that can be instantiated",
|
||||
);
|
||||
let instance = Type::instance(db, class.unknown_specialization(db));
|
||||
let class_definition = class.definition(db);
|
||||
let typevar = TypeVarInstance::new(
|
||||
db,
|
||||
|
||||
@@ -3086,14 +3086,20 @@ impl<'db> BindingError<'db> {
|
||||
String::new()
|
||||
}
|
||||
));
|
||||
diag.set_primary_message(format_args!(
|
||||
"Argument type `{argument_ty_display}` does not satisfy {} of type variable `{}`",
|
||||
match error {
|
||||
SpecializationError::MismatchedBound {..} => "upper bound",
|
||||
SpecializationError::MismatchedConstraint {..} => "constraints",
|
||||
},
|
||||
typevar.name(context.db()),
|
||||
));
|
||||
|
||||
let typevar_name = typevar.name(context.db());
|
||||
match error {
|
||||
SpecializationError::MismatchedBound { .. } => {
|
||||
diag.set_primary_message(format_args!("Argument type `{argument_ty_display}` does not satisfy upper bound of type variable `{typevar_name}` ({})",
|
||||
typevar.upper_bound(context.db()).expect("type variable should have an upper bound if this error occurs").display(context.db())
|
||||
));
|
||||
}
|
||||
SpecializationError::MismatchedConstraint { .. } => {
|
||||
diag.set_primary_message(format_args!("Argument type `{argument_ty_display}` does not satisfy constraints of type variable `{typevar_name}` ({})",
|
||||
typevar.constraints(context.db()).expect("type variable should have constraints if this error occurs").iter().map(|ty| ty.display(context.db())).join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(typevar_definition) = typevar.definition(context.db()) {
|
||||
let module = parsed_module(context.db(), typevar_definition.file(context.db()))
|
||||
|
||||
@@ -1150,11 +1150,13 @@ 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() {
|
||||
write!(
|
||||
f,
|
||||
": {}",
|
||||
annotated_type.display_with(self.db, self.settings)
|
||||
)?;
|
||||
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() {
|
||||
@@ -1167,7 +1169,9 @@ 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.
|
||||
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(())
|
||||
}
|
||||
|
||||
@@ -15,18 +15,49 @@ use std::{collections::HashMap, slice::Iter};
|
||||
use itertools::{EitherOrBoth, Itertools};
|
||||
use smallvec::{SmallVec, smallvec_inline};
|
||||
|
||||
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
|
||||
use crate::semantic_index::definition::Definition;
|
||||
use super::{
|
||||
DynamicType, Type, TypeVarVariance, definition_expression_type, infer_definition_types,
|
||||
semantic_index,
|
||||
};
|
||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
||||
use crate::semantic_index::scope::ScopeId;
|
||||
use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension};
|
||||
use crate::types::generics::{GenericContext, walk_generic_context};
|
||||
use crate::types::function::FunctionType;
|
||||
use crate::types::generics::{GenericContext, bind_typevar, 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,
|
||||
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
|
||||
VarianceInferable, todo_type,
|
||||
};
|
||||
use crate::{Db, FxOrderSet};
|
||||
use ruff_db::parsed::parsed_module;
|
||||
use ruff_python_ast::{self as ast, name::Name};
|
||||
|
||||
fn infer_method_type<'db>(
|
||||
db: &'db dyn Db,
|
||||
definition: Definition<'db>,
|
||||
) -> Option<FunctionType<'db>> {
|
||||
let class_scope_id = definition.scope(db);
|
||||
let file = class_scope_id.file(db);
|
||||
let index = semantic_index(db, file);
|
||||
let module = parsed_module(db, file).load(db);
|
||||
|
||||
let DefinitionKind::Function(func_def) = definition.kind(db) else {
|
||||
return None;
|
||||
};
|
||||
let class_scope = index.scope(class_scope_id.file_scope_id(db));
|
||||
class_scope.node().as_class()?;
|
||||
|
||||
let method_definition = index.expect_single_definition(func_def.node(&module));
|
||||
let func_type = infer_definition_types(db, method_definition)
|
||||
.declaration_type(method_definition)
|
||||
.inner_type()
|
||||
.into_function_literal()?;
|
||||
Some(func_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)]
|
||||
@@ -1207,16 +1238,78 @@ impl<'db> Parameters<'db> {
|
||||
);
|
||||
}
|
||||
|
||||
let method_type = infer_method_type(db, definition);
|
||||
let is_method = method_type.is_some();
|
||||
let is_classmethod = method_type.is_some_and(|f| f.is_classmethod(db));
|
||||
let is_staticmethod = method_type.is_some_and(|f| f.is_staticmethod(db));
|
||||
|
||||
// TODO: remove duplication with Type::in_type_expression
|
||||
let get_self_type = |scope_id: ScopeId, typevar_binding_context| {
|
||||
{
|
||||
let module = parsed_module(db, scope_id.file(db)).load(db);
|
||||
let index = semantic_index(db, scope_id.file(db));
|
||||
let class = nearest_enclosing_class(db, index, scope_id).unwrap();
|
||||
let instance = Type::ClassLiteral(class)
|
||||
.to_instance(db)
|
||||
.expect("nearest_enclosing_class must return type that can be instantiated");
|
||||
let class_definition = class.definition(db);
|
||||
let typevar = TypeVarInstance::new(
|
||||
db,
|
||||
ast::name::Name::new_static("Self"),
|
||||
Some(class_definition),
|
||||
Some(TypeVarBoundOrConstraints::UpperBound(instance).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,
|
||||
);
|
||||
Type::TypeVar(
|
||||
bind_typevar(
|
||||
db,
|
||||
&module,
|
||||
index,
|
||||
scope_id.file_scope_id(db),
|
||||
typevar_binding_context,
|
||||
typevar,
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
|
||||
Parameter::from_node_and_kind(
|
||||
db,
|
||||
definition,
|
||||
&arg.parameter,
|
||||
ParameterKind::PositionalOrKeyword {
|
||||
name: arg.parameter.name.id.clone(),
|
||||
default_type: default_type(arg),
|
||||
},
|
||||
)
|
||||
if is_method
|
||||
&& !is_staticmethod
|
||||
&& !is_classmethod
|
||||
&& arg.parameter.annotation().is_none()
|
||||
&& parameters.index(arg.name().id()) == Some(0)
|
||||
{
|
||||
let scope_id = definition.scope(db);
|
||||
let typevar_binding_context = Some(definition);
|
||||
let implicit_annotation = get_self_type(scope_id, typevar_binding_context);
|
||||
|
||||
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),
|
||||
},
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let variadic = vararg.as_ref().map(|arg| {
|
||||
@@ -1378,6 +1471,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,
|
||||
}
|
||||
@@ -1386,6 +1483,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,
|
||||
@@ -1397,6 +1495,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,
|
||||
@@ -1408,6 +1507,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,
|
||||
}
|
||||
@@ -1416,6 +1516,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,
|
||||
@@ -1427,6 +1528,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,
|
||||
}
|
||||
@@ -1465,6 +1567,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,
|
||||
}
|
||||
}
|
||||
@@ -1480,6 +1583,7 @@ impl<'db> Parameter<'db> {
|
||||
) -> Self {
|
||||
let Parameter {
|
||||
annotated_type,
|
||||
synthetic_annotation,
|
||||
kind,
|
||||
form,
|
||||
} = self;
|
||||
@@ -1523,6 +1627,7 @@ impl<'db> Parameter<'db> {
|
||||
|
||||
Self {
|
||||
annotated_type: Some(annotated_type),
|
||||
synthetic_annotation: *synthetic_annotation,
|
||||
kind,
|
||||
form: *form,
|
||||
}
|
||||
@@ -1543,6 +1648,7 @@ impl<'db> Parameter<'db> {
|
||||
}),
|
||||
kind,
|
||||
form: ParameterForm::Value,
|
||||
synthetic_annotation: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1597,6 +1703,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 {
|
||||
|
||||
Reference in New Issue
Block a user