[ty] Infer type of self as typing.Self in method body (#18473)

Part of https://github.com/astral-sh/ty/issues/159

Add support for adding a synthetic `typing.Self` type for `self`
arguments in methods.
`typing.Self` is assigned as the type if there are no annotations.

- Updated md tests.

---------

Co-authored-by: David Peter <mail@david-peter.de>
This commit is contained in:
Shaygan Hooshyari
2025-10-16 16:39:17 +02:00
committed by David Peter
parent 596ee17ec3
commit 195669f33d
15 changed files with 288 additions and 203 deletions

View File

@@ -206,7 +206,7 @@ enum ReduceResult<'db> {
//
// For now (until we solve https://github.com/astral-sh/ty/issues/957), keep this number
// below 200, which is the salsa fixpoint iteration limit.
const MAX_UNION_LITERALS: usize = 199;
const MAX_UNION_LITERALS: usize = 190;
pub(crate) struct UnionBuilder<'db> {
elements: Vec<UnionElement<'db>>,

View File

@@ -80,11 +80,11 @@ pub(crate) fn bind_typevar<'db>(
/// Create a `typing.Self` type variable for a given class.
pub(crate) fn typing_self<'db>(
db: &'db dyn Db,
scope_id: ScopeId,
function_scope_id: ScopeId,
typevar_binding_context: Option<Definition<'db>>,
class: ClassLiteral<'db>,
) -> Option<Type<'db>> {
let index = semantic_index(db, scope_id.file(db));
let index = semantic_index(db, function_scope_id.file(db));
let identity = TypeVarIdentity::new(
db,
@@ -110,7 +110,7 @@ pub(crate) fn typing_self<'db>(
bind_typevar(
db,
index,
scope_id.file_scope_id(db),
function_scope_id.file_scope_id(db),
typevar_binding_context,
typevar,
)

View File

@@ -3,7 +3,7 @@ use std::{iter, mem};
use itertools::{Either, Itertools};
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity};
use ruff_db::files::File;
use ruff_db::parsed::ParsedModuleRef;
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast::visitor::{Visitor, walk_expr};
use ruff_python_ast::{self as ast, AnyNodeRef, ExprContext, PythonVersion};
use ruff_python_stdlib::builtins::version_builtin_was_added;
@@ -81,9 +81,9 @@ use crate::types::function::{
};
use crate::types::generics::{
GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar,
enclosing_generic_contexts,
enclosing_generic_contexts, typing_self,
};
use crate::types::infer::nearest_enclosing_function;
use crate::types::infer::{nearest_enclosing_class, nearest_enclosing_function};
use crate::types::instance::SliceLiteral;
use crate::types::mro::MroErrorKind;
use crate::types::signatures::Signature;
@@ -2495,6 +2495,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
} else {
let ty = if let Some(default_ty) = default_ty {
UnionType::from_elements(self.db(), [Type::unknown(), default_ty])
} else if let Some(ty) = self.special_first_method_parameter_type(parameter) {
ty
} else {
Type::unknown()
};
@@ -2535,6 +2537,53 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
/// Special case for unannotated `cls` and `self` arguments to class methods and instance methods.
fn special_first_method_parameter_type(
&mut self,
parameter: &ast::Parameter,
) -> Option<Type<'db>> {
let db = self.db();
let scope_id = self.scope();
let file = scope_id.file(db);
let function_scope = scope_id.scope(db);
let method = function_scope.node().as_function()?;
let parent_scope_id = function_scope.parent()?;
let parent_scope = self.index.scope(parent_scope_id);
parent_scope.node().as_class()?;
let method_definition = self.index.expect_single_definition(method);
let DefinitionKind::Function(function_definition) = method_definition.kind(db) else {
return None;
};
let func_type = infer_definition_types(db, method_definition)
.declaration_type(method_definition)
.inner_type()
.as_function_literal()?;
let module = parsed_module(db, file).load(db);
if function_definition
.node(&module)
.parameters
.index(parameter.name())
.is_some_and(|index| index != 0)
{
return None;
}
if func_type.is_classmethod(db) {
// TODO: set the type for `cls` argument
return None;
} else if func_type.is_staticmethod(db) {
return None;
}
let class = nearest_enclosing_class(db, self.index, scope_id).unwrap();
typing_self(db, self.scope(), Some(method_definition), class)
}
/// Set initial declared/inferred types for a `*args` variadic positional parameter.
///
/// The annotated type is implicitly wrapped in a string-keyed dictionary.