diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 279ce5a5bb..da74001846 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -253,4 +253,23 @@ reveal_type(D().instance_method) reveal_type(D.class_method) ``` +## Test + +```py +from ty_extensions import generic_context +from typing import Generic, TypeVar + +T = TypeVar("T") +U = TypeVar("U") + +class C(Generic[T]): + def method(self, u: int) -> int: + return u + + def generic_method(self, t: T, u: U) -> U: + return u + +reveal_type(generic_context(C.method)) # revealed: tuple[Self@method] +``` + [self attribute]: https://typing.python.org/en/latest/spec/generics.html#use-in-attribute-annotations diff --git a/crates/ty_python_semantic/resources/mdtest/call/methods.md b/crates/ty_python_semantic/resources/mdtest/call/methods.md index 652d73493e..9185e1722e 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/methods.md +++ b/crates/ty_python_semantic/resources/mdtest/call/methods.md @@ -69,9 +69,7 @@ reveal_type(bound_method(1)) # revealed: str When we call the function object itself, we need to pass the `instance` explicitly: ```py -# error: [invalid-argument-type] -# error: [missing-argument] -C.f(1) +C.f(1) # error: [missing-argument] reveal_type(C.f(C(), 1)) # revealed: str ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md index 234de56a8c..17e12d9150 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/legacy/classes.md @@ -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 diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md index 467b6ed42c..53f652d37a 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/classes.md @@ -395,6 +395,7 @@ class C[T]: def __init__[S](self, x: T, y: S) -> None: ... +# TODO: need to handle self in __init__ call. reveal_type(C(1, 1)) # revealed: C[int] reveal_type(C(1, "string")) # revealed: C[int] reveal_type(C(1, True)) # revealed: C[int] @@ -511,16 +512,16 @@ 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.method)) # revealed: tuple[Self@method] reveal_type(generic_context(C.generic_method)) # revealed: tuple[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].method)) # revealed: tuple[Self@method] reveal_type(generic_context(C[int].generic_method)) # revealed: tuple[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.method)) # revealed: tuple[Self@method] reveal_type(generic_context(c.generic_method)) # revealed: tuple[U@generic_method] ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md index 02142d006b..6979f918fc 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/scoping.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/scoping.md @@ -154,7 +154,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: @@ -165,6 +165,10 @@ class C[T]: return y c: C[int] = C() +reveal_type(c) # revealed: C[Unknown] +# reveal_type(c) # revealed: C[Unknown] +# TODO: Next line fails. The reason is the reveal type above +# error: 13 [invalid-argument-type] "Argument to bound method `m` is incorrect: Expected `Self@m`, found `C[Unknown]`" reveal_type(c.m(1, "string")) # revealed: Literal["string"] ``` diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 3e96f2acad..8528cc41f0 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -13,42 +13,41 @@ use std::{collections::HashMap, slice::Iter}; use itertools::EitherOrBoth; -use ruff_db::parsed::parsed_module; use smallvec::{SmallVec, smallvec_inline}; -use super::TypeVarVariance; use super::{ - DynamicType, Type, definition_expression_type, infer_definition_types, semantic_index, + DynamicType, Type, TypeVarVariance, definition_expression_type, infer_definition_types, + semantic_index, }; -use crate::semantic_index::definition::Definition; +use crate::semantic_index::definition::{Definition, DefinitionKind}; use crate::types::constraints::{ConstraintSet, Constraints, IteratorConstraintsExtension}; use crate::types::function::FunctionType; -use crate::types::generics::GenericContext; -use crate::types::generics::walk_generic_context; +use crate::types::generics::{GenericContext, walk_generic_context}; use crate::types::{ ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor, SpecialFormType, TypeMapping, TypeRelation, 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> { - let scope_id = definition.scope(db); - let file = scope_id.file(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 method_scope = index.scope(scope_id.file_scope_id(db)); - let method = method_scope.node().as_function(&module)?; - let parent_scope_id = method_scope.parent()?; - let parent_scope = index.scope(parent_scope_id); - parent_scope.node().as_class(&module)?; + 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(&module)?; - let method_definition = index.expect_single_definition(method); + 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() @@ -384,6 +383,7 @@ impl<'db> Signature<'db> { ) -> Self { let parameters = Parameters::from_parameters(db, definition, function_node.parameters.as_ref()); + let return_ty = function_node.returns.as_ref().map(|returns| { let plain_return_ty = definition_expression_type(db, definition, returns.as_ref()) .apply_type_mapping( @@ -1194,17 +1194,30 @@ 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)); let positional_or_keyword = args.iter().map(|arg| { // TODO(https://github.com/astral-sh/ty/issues/159): Also set the type for `cls` argument - if !is_staticmethod + // eprintln!( + // "arg {}: pos: {:?}, is_static: {}, is_class: {}, anno: {:?}", + // arg.name().id.as_str(), + // parameters.index(arg.name().id()), + // is_staticmethod, + // is_classmethod, + // arg.parameter.annotation() + // ); + // dbg!(is_method); + if is_method + && !is_staticmethod && !is_classmethod && arg.parameter.annotation().is_none() && parameters.index(arg.name().id()) == Some(0) { + eprintln!("arg {} is self", arg.name().id.as_str()); let implicit_annotation = Type::SpecialForm(SpecialFormType::TypingSelf) .in_type_expression(db, definition.scope(db), Some(definition)) .ok(); @@ -1218,6 +1231,7 @@ impl<'db> Parameters<'db> { form: ParameterForm::Value, } } else { + eprintln!("arg {} is not self", arg.name().id.as_str()); Parameter::from_node_and_kind( db, definition,