diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 031e6c4733..5f07a5eee7 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -141,8 +141,7 @@ class B: reveal_type(B().name_does_not_matter()) # revealed: B -# TODO: this should be B -reveal_type(B().positional_only(1)) # revealed: Unknown +reveal_type(B().positional_only(1)) # revealed: B reveal_type(B().keyword_only(x=1)) # revealed: B diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index dd996c4098..b96b953e20 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -664,7 +664,7 @@ def combine(p: Person, e: Employee): reveal_type(e | e) # revealed: Employee # TODO: Should be `Person` once we support subtyping for TypedDicts - reveal_type(p | e) # revealed: Employee + reveal_type(p | e) # revealed: Person | Employee ``` When inheriting from a `TypedDict` with a different `total` setting, inherited fields maintain their diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index df6309e3f4..06670606c3 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -13,6 +13,7 @@ use std::{collections::HashMap, slice::Iter}; use itertools::{EitherOrBoth, Itertools}; +use ruff_python_ast::ParameterWithDefault; use smallvec::{SmallVec, smallvec_inline}; use super::{ @@ -1225,16 +1226,83 @@ impl<'db> Parameters<'db> { .map(|default| definition_expression_type(db, definition, default)) }; + let method_info = infer_method_information(db, definition); + let is_static_or_classmethod = method_info + .is_some_and(|f| f.method.is_staticmethod(db) || f.method.is_classmethod(db)); + + let inferred_annotation = |arg: &ParameterWithDefault| { + if let Some(MethodInformation { method, class }) = method_info + && !is_static_or_classmethod + && arg.parameter.annotation().is_none() + && parameters.index(arg.name().id()) == Some(0) + // TODO: find out why we break protocol type property tests when we include them here: + && !class.is_protocol(db) + { + 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).is_self(db)) + } else { + false + } + }); + + if method_has_self_in_generic_context + || class.is_generic() + || class.known(db).is_some_and(KnownClass::is_fallback_class) + { + 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(); + + Some( + Type::NonInferableTypeVar( + typing_self(db, scope_id, typevar_binding_context, class).unwrap(), + ) + .apply_type_mapping( + db, + &TypeMapping::MarkTypeVarsInferable(Some(BindingContext::Definition( + definition, + ))), + ), + ) + } else { + // For methods of non-generic classes that are not otherwise generic (e.g. return `Self` or + // have additional type parameters), the implicit `Self` type of the `self` parameter would + // be the only type variable, so we can just use the class directly. + Some(Type::instance(db, class)) + } + } else { + None + } + }; + let pos_only_param = |param: &ast::ParameterWithDefault| { - Parameter::from_node_and_kind( - db, - definition, - ¶m.parameter, - ParameterKind::PositionalOnly { - name: Some(param.parameter.name.id.clone()), - default_type: default_type(param), - }, - ) + if let Some(inferred_annotation_type) = inferred_annotation(param) { + return Parameter { + annotated_type: Some(inferred_annotation_type), + inferred_annotation: true, + kind: ParameterKind::PositionalOnly { + name: Some(param.parameter.name.id.clone()), + default_type: default_type(param), + }, + form: ParameterForm::Value, + }; + } else { + Parameter::from_node_and_kind( + db, + definition, + ¶m.parameter, + ParameterKind::PositionalOnly { + name: Some(param.parameter.name.id.clone()), + default_type: default_type(param), + }, + ) + } }; let mut positional_only: Vec = posonlyargs.iter().map(pos_only_param).collect(); @@ -1256,54 +1324,11 @@ impl<'db> Parameters<'db> { .map(pos_only_param), ); } - let method_info = infer_method_information(db, definition); - let is_static_or_classmethod = method_info - .is_some_and(|f| f.method.is_staticmethod(db) || f.method.is_classmethod(db)); let positional_or_keyword = pos_or_keyword_iter.map(|arg| { - if let Some(MethodInformation { method, class }) = method_info - && !is_static_or_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).is_self(db)) - } else { - false - } - }); - - let inferred_annotation = if method_has_self_in_generic_context - || class.is_generic() - || class.known(db).is_some_and(KnownClass::is_fallback_class) - { - 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::NonInferableTypeVar( - typing_self(db, scope_id, typevar_binding_context, class).unwrap(), - ) - .apply_type_mapping( - db, - &TypeMapping::MarkTypeVarsInferable(Some(BindingContext::Definition( - definition, - ))), - ) - } else { - // For methods of non-generic classes that are not otherwise generic (e.g. return `Self` or - // have additional type parameters), the implicit `Self` type of the `self` parameter would - // be the only type variable, so we can just use the class directly. - Type::instance(db, class) - }; - + if let Some(inferred_annotation_type) = inferred_annotation(arg) { Parameter { - annotated_type: Some(inferred_annotation), + annotated_type: Some(inferred_annotation_type), inferred_annotation: true, kind: ParameterKind::PositionalOrKeyword { name: arg.parameter.name.id.clone(),