Also infer Self for positional-only self parameters

This commit is contained in:
David Peter 2025-09-24 18:06:51 +02:00
parent 2a2e03c3b9
commit e76d5233a9
3 changed files with 81 additions and 57 deletions

View file

@ -141,8 +141,7 @@ class B:
reveal_type(B().name_does_not_matter()) # revealed: B reveal_type(B().name_does_not_matter()) # revealed: B
# TODO: this should be B reveal_type(B().positional_only(1)) # revealed: B
reveal_type(B().positional_only(1)) # revealed: Unknown
reveal_type(B().keyword_only(x=1)) # revealed: B reveal_type(B().keyword_only(x=1)) # revealed: B

View file

@ -664,7 +664,7 @@ def combine(p: Person, e: Employee):
reveal_type(e | e) # revealed: Employee reveal_type(e | e) # revealed: Employee
# TODO: Should be `Person` once we support subtyping for TypedDicts # 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 When inheriting from a `TypedDict` with a different `total` setting, inherited fields maintain their

View file

@ -13,6 +13,7 @@
use std::{collections::HashMap, slice::Iter}; use std::{collections::HashMap, slice::Iter};
use itertools::{EitherOrBoth, Itertools}; use itertools::{EitherOrBoth, Itertools};
use ruff_python_ast::ParameterWithDefault;
use smallvec::{SmallVec, smallvec_inline}; use smallvec::{SmallVec, smallvec_inline};
use super::{ use super::{
@ -1225,16 +1226,83 @@ impl<'db> Parameters<'db> {
.map(|default| definition_expression_type(db, definition, default)) .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| { let pos_only_param = |param: &ast::ParameterWithDefault| {
Parameter::from_node_and_kind( if let Some(inferred_annotation_type) = inferred_annotation(param) {
db, return Parameter {
definition, annotated_type: Some(inferred_annotation_type),
&param.parameter, inferred_annotation: true,
ParameterKind::PositionalOnly { kind: ParameterKind::PositionalOnly {
name: Some(param.parameter.name.id.clone()), name: Some(param.parameter.name.id.clone()),
default_type: default_type(param), default_type: default_type(param),
}, },
) form: ParameterForm::Value,
};
} else {
Parameter::from_node_and_kind(
db,
definition,
&param.parameter,
ParameterKind::PositionalOnly {
name: Some(param.parameter.name.id.clone()),
default_type: default_type(param),
},
)
}
}; };
let mut positional_only: Vec<Parameter> = posonlyargs.iter().map(pos_only_param).collect(); let mut positional_only: Vec<Parameter> = posonlyargs.iter().map(pos_only_param).collect();
@ -1256,54 +1324,11 @@ impl<'db> Parameters<'db> {
.map(pos_only_param), .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| { let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
if let Some(MethodInformation { method, class }) = method_info if let Some(inferred_annotation_type) = inferred_annotation(arg) {
&& !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)
};
Parameter { Parameter {
annotated_type: Some(inferred_annotation), annotated_type: Some(inferred_annotation_type),
inferred_annotation: true, inferred_annotation: true,
kind: ParameterKind::PositionalOrKeyword { kind: ParameterKind::PositionalOrKeyword {
name: arg.parameter.name.id.clone(), name: arg.parameter.name.id.clone(),