Make Self an inferrable TypeVar

This commit is contained in:
David Peter 2025-09-08 13:03:38 +02:00
parent 4064fa28fc
commit a66add41be
8 changed files with 62 additions and 15 deletions

View File

@ -79,8 +79,7 @@ class A:
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: Unknown
reveal_type(a.implicit_self()) # revealed: A
reveal_type(a.implicit_self) # revealed: bound method A.implicit_self() -> A
```
@ -130,8 +129,7 @@ class G(Generic[T]):
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: Unknown
reveal_type(G[int].id(g)) # revealed: G[int]
```
Free functions and nested functions do not use implicit `Self`:

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: 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
```

View File

@ -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()

View File

@ -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]):

View File

@ -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
```

View File

@ -358,9 +358,11 @@ 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)
```

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

@ -20,13 +20,16 @@ use super::{
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::function::FunctionType;
use crate::types::generics::{GenericContext, walk_generic_context};
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,
SpecialFormType, TypeMapping, TypeRelation, VarianceInferable, todo_type,
TypeMapping, TypeRelation, TypeVarBoundOrConstraints, TypeVarInstance, TypeVarKind,
VarianceInferable, todo_type,
};
use crate::{Db, FxOrderSet};
use ruff_db::parsed::parsed_module;
@ -1227,6 +1230,42 @@ impl<'db> Parameters<'db> {
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| {
if is_method
&& !is_staticmethod
@ -1234,11 +1273,12 @@ impl<'db> Parameters<'db> {
&& arg.parameter.annotation().is_none()
&& parameters.index(arg.name().id()) == Some(0)
{
let implicit_annotation = Type::SpecialForm(SpecialFormType::TypingSelf)
.in_type_expression(db, definition.scope(db), Some(definition))
.ok();
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: implicit_annotation,
annotated_type: Some(implicit_annotation),
synthetic_annotation: true,
kind: ParameterKind::PositionalOrKeyword {
name: arg.parameter.name.id.clone(),