From 178f48fc0b57cc0cb081afe19ec8cf96ea793625 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Thu, 4 Sep 2025 20:11:16 +0200 Subject: [PATCH] Update way to identify self in signature --- crates/ruff_python_ast/src/nodes.rs | 6 + .../resources/mdtest/annotations/self.md | 10 +- .../ty_python_semantic/src/types/display.rs | 4 +- .../src/types/signatures.rs | 127 ++++++++---------- 4 files changed, 67 insertions(+), 80 deletions(-) diff --git a/crates/ruff_python_ast/src/nodes.rs b/crates/ruff_python_ast/src/nodes.rs index 6a824603bb..8064572753 100644 --- a/crates/ruff_python_ast/src/nodes.rs +++ b/crates/ruff_python_ast/src/nodes.rs @@ -3030,6 +3030,12 @@ impl Parameters { .find(|arg| arg.parameter.name.as_str() == name) } + /// Returns the index of the parameter with the given name + pub fn index(&self, name: &str) -> Option { + self.iter_non_variadic_params() + .position(|arg| arg.parameter.name.as_str() == name) + } + /// Returns an iterator over all parameters included in this [`Parameters`] node. pub fn iter(&self) -> ParametersIterator<'_> { ParametersIterator::new(self) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/self.md b/crates/ty_python_semantic/resources/mdtest/annotations/self.md index 3535d65813..279ce5a5bb 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/self.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/self.md @@ -30,9 +30,7 @@ class Shape: def nested_func_without_enclosing_binding(self): def inner(x: Self): - # TODO: revealed: Self@nested_func_without_enclosing_binding - # (The outer method binds an implicit `Self`) - reveal_type(x) # revealed: Self@inner + reveal_type(x) # revealed: Self@nested_func_without_enclosing_binding inner(self) def implicit_self(self) -> Self: @@ -82,8 +80,10 @@ class A: def bar(cls) -> int: return 1 -reveal_type(A().implicit_self()) # revealed: A -reveal_type(A.implicit_self) # revealed: def implicit_self(self) -> Self +# TODO: revealed: A +# Requires implicit in method body detection +reveal_type(A().implicit_self()) # revealed: Unknown +reveal_type(A.implicit_self) # revealed: def implicit_self(self) -> Self@implicit_self ``` ## typing_extensions diff --git a/crates/ty_python_semantic/src/types/display.rs b/crates/ty_python_semantic/src/types/display.rs index cf9669f0ad..5764c3fd00 100644 --- a/crates/ty_python_semantic/src/types/display.rs +++ b/crates/ty_python_semantic/src/types/display.rs @@ -1150,7 +1150,7 @@ impl Display for DisplayParameter<'_> { if let Some(name) = self.param.display_name() { f.write_str(&name)?; if let Some(annotated_type) = self.param.annotated_type() { - if !self.param.type_inffered() { + if !self.param.has_synthetic_annotation() { write!( f, ": {}", @@ -1169,7 +1169,7 @@ impl Display for DisplayParameter<'_> { } else if let Some(ty) = self.param.annotated_type() { // This case is specifically for the `Callable` signature where name and default value // cannot be provided. - if !self.param.type_inffered() { + if !self.param.has_synthetic_annotation() { ty.display_with(self.db, self.settings).fmt(f)?; } } diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index df06a663d4..3e96f2acad 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -13,31 +13,49 @@ 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, FunctionDecorators, KnownInstanceType, Type, definition_expression_type, - infer_definition_types, semantic_index, + DynamicType, Type, definition_expression_type, infer_definition_types, semantic_index, }; -use super::{DynamicType, Type, TypeVarVariance, definition_expression_type}; -use super::{DynamicType, Type, definition_expression_type}; use crate::semantic_index::definition::Definition; -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::GenericContext; -use crate::types::generics::{GenericContext, walk_generic_context}; +use crate::types::generics::walk_generic_context; use crate::types::{ ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor, - TypeMapping, TypeRelation, VarianceInferable, todo_type, + SpecialFormType, TypeMapping, TypeRelation, VarianceInferable, todo_type, }; -use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance, todo_type}; -use crate::types::{ClassLiteral, TypeMapping, TypeVarInstance, todo_type}; use crate::{Db, FxOrderSet}; 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 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 method_definition = index.expect_single_definition(method); + let func_type = infer_definition_types(db, method_definition) + .declaration_type(method_definition) + .inner_type() + .into_function_literal()?; + Some(func_type) +} + /// The signature of a single callable. If the callable is overloaded, there is a separate /// [`Signature`] for each overload. #[derive(Clone, Debug, PartialEq, Eq, Hash, salsa::Update, get_size2::GetSize)] @@ -1176,44 +1194,23 @@ impl<'db> Parameters<'db> { }, ) }); - let function_is_method = if matches!(definition.kind(db), DefinitionKind::Function(_)) { - let scope = definition.scope(db); - let index = semantic_index(db, scope.file(db)); - let current_scope = index.scope(scope.file_scope_id(db)); - // class_context_of_current_method(db, index, scope).is_some() - current_scope.kind().is_class() - } else { - false - }; - let classmethod = if let DefinitionKind::Function(f) = definition.kind(db) { - if matches!(f.name.id().as_str(), "__new__" | "__class_getitem__") { - true - } else { - let result = infer_definition_types(db, definition); - match result.declaration_type(definition).inner_type() { - Type::FunctionLiteral(t) => { - t.decorators(db).contains(FunctionDecorators::CLASSMETHOD) - } - _ => false, - } - } - } else { - false - }; + let method_type = infer_method_type(db, definition); + 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().enumerate().map(|(index, arg)| { - if index == 0 - && function_is_method + 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 + && !is_classmethod && arg.parameter.annotation().is_none() - // TODO: Handle case when cls is not annotated - && !classmethod + && parameters.index(arg.name().id()) == Some(0) { - let implicit_annotation = Type::KnownInstance(KnownInstanceType::TypingSelf) - .in_type_expression(db, definition.scope(db)) - .unwrap(); + let implicit_annotation = Type::SpecialForm(SpecialFormType::TypingSelf) + .in_type_expression(db, definition.scope(db), Some(definition)) + .ok(); Parameter { - annotated_type: Some(implicit_annotation), - type_inffered: true, + annotated_type: implicit_annotation, + synthetic_annotation: true, kind: ParameterKind::PositionalOrKeyword { name: arg.parameter.name.id.clone(), default_type: default_type(arg), @@ -1389,7 +1386,7 @@ pub(crate) struct Parameter<'db> { /// If the type of parameter was inferred e.g. the first argument of a method has type /// `typing.Self`. - type_inffered: bool, + synthetic_annotation: bool, kind: ParameterKind<'db>, pub(crate) form: ParameterForm, @@ -1399,7 +1396,7 @@ impl<'db> Parameter<'db> { pub(crate) fn positional_only(name: Option) -> Self { Self { annotated_type: None, - type_inffered: false, + synthetic_annotation: false, kind: ParameterKind::PositionalOnly { name, default_type: None, @@ -1411,7 +1408,7 @@ impl<'db> Parameter<'db> { pub(crate) fn positional_or_keyword(name: Name) -> Self { Self { annotated_type: None, - type_inffered: false, + synthetic_annotation: false, kind: ParameterKind::PositionalOrKeyword { name, default_type: None, @@ -1423,7 +1420,7 @@ impl<'db> Parameter<'db> { pub(crate) fn variadic(name: Name) -> Self { Self { annotated_type: None, - type_inffered: false, + synthetic_annotation: false, kind: ParameterKind::Variadic { name }, form: ParameterForm::Value, } @@ -1432,7 +1429,7 @@ impl<'db> Parameter<'db> { pub(crate) fn keyword_only(name: Name) -> Self { Self { annotated_type: None, - type_inffered: false, + synthetic_annotation: false, kind: ParameterKind::KeywordOnly { name, default_type: None, @@ -1444,7 +1441,7 @@ impl<'db> Parameter<'db> { pub(crate) fn keyword_variadic(name: Name) -> Self { Self { annotated_type: None, - type_inffered: false, + synthetic_annotation: false, kind: ParameterKind::KeywordVariadic { name }, form: ParameterForm::Value, } @@ -1483,7 +1480,7 @@ impl<'db> Parameter<'db> { .annotated_type .map(|ty| ty.apply_type_mapping_impl(db, type_mapping, visitor)), kind: self.kind.apply_type_mapping_impl(db, type_mapping, visitor), - type_inffered: self.type_inffered, + synthetic_annotation: self.synthetic_annotation, form: self.form, } } @@ -1499,7 +1496,7 @@ impl<'db> Parameter<'db> { ) -> Self { let Parameter { annotated_type, - type_inffered, + synthetic_annotation, kind, form, } = self; @@ -1542,14 +1539,8 @@ impl<'db> Parameter<'db> { }; Self { -<<<<<<< HEAD annotated_type: Some(annotated_type), -||||||| parent of 63531eab6 (Don't display the implicit typing.Self type) - annotated_type, -======= - annotated_type, - type_inffered: *type_inffered, ->>>>>>> 63531eab6 (Don't display the implicit typing.Self type) + synthetic_annotation: *synthetic_annotation, kind, form: *form, } @@ -1562,25 +1553,15 @@ impl<'db> Parameter<'db> { kind: ParameterKind<'db>, ) -> Self { Self { -<<<<<<< HEAD annotated_type: parameter.annotation().map(|annotation| { definition_expression_type(db, definition, annotation).apply_type_mapping( db, &TypeMapping::MarkTypeVarsInferable(BindingContext::Definition(definition)), ) }), -||||||| parent of 63531eab6 (Don't display the implicit typing.Self type) - annotated_type: parameter - .annotation() - .map(|annotation| definition_expression_type(db, definition, annotation)), -======= - annotated_type: parameter - .annotation() - .map(|annotation| definition_expression_type(db, definition, annotation)), - type_inffered: false, ->>>>>>> 63531eab6 (Don't display the implicit typing.Self type) kind, form: ParameterForm::Value, + synthetic_annotation: false, } } @@ -1636,8 +1617,8 @@ impl<'db> Parameter<'db> { } /// Whether the type of the parameter was inferred. - pub(crate) fn type_inffered(&self) -> bool { - self.type_inffered + pub(crate) fn has_synthetic_annotation(&self) -> bool { + self.synthetic_annotation } /// Name of the parameter (if it has one).