diff --git a/crates/ty_python_semantic/resources/mdtest/cycle.md b/crates/ty_python_semantic/resources/mdtest/cycle.md index 7d1686fb2dbbc4..d0091a81a13b3f 100644 --- a/crates/ty_python_semantic/resources/mdtest/cycle.md +++ b/crates/ty_python_semantic/resources/mdtest/cycle.md @@ -87,38 +87,28 @@ class C: def inner_a(positional=self.a): return self.a = inner_a - # revealed: def inner_a(positional=Unknown | (def inner_a(positional=Unknown) -> Unknown)) -> Unknown + # revealed: Divergent reveal_type(inner_a) def inner_b(*, kw_only=self.b): return self.b = inner_b - # revealed: def inner_b(*, kw_only=Unknown | (def inner_b(*, kw_only=Unknown) -> Unknown)) -> Unknown + # revealed: Divergent reveal_type(inner_b) def inner_c(positional_only=self.c, /): return self.c = inner_c - # revealed: def inner_c(positional_only=Unknown | (def inner_c(positional_only=Unknown, /) -> Unknown), /) -> Unknown + # revealed: Divergent reveal_type(inner_c) def inner_d(*, kw_only=self.d): return self.d = inner_d - # revealed: def inner_d(*, kw_only=Unknown | (def inner_d(*, kw_only=Unknown) -> Unknown)) -> Unknown + # revealed: Divergent reveal_type(inner_d) ``` -We do, however, still check assignability of the default value to the parameter type: - -```py -class D: - def f(self: "D"): - # error: [invalid-parameter-default] "Default value of type `Unknown | (def inner_a(a: int = Unknown | (def inner_a(a: int = Unknown) -> Unknown)) -> Unknown)` is not assignable to annotated parameter type `int`" - def inner_a(a: int = self.a): ... - self.a = inner_a -``` - ### Lambdas ```py @@ -129,15 +119,15 @@ class C: self.c = lambda positional_only=self.c, /: positional_only self.d = lambda *, kw_only=self.d: kw_only - # revealed: (positional=Unknown | ((positional=Unknown) -> Unknown)) -> Unknown + # revealed: (positional=Unknown | Divergent) -> Unknown reveal_type(self.a) - # revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown + # revealed: (*, kw_only=Unknown | Divergent) -> Unknown reveal_type(self.b) - # revealed: (positional_only=Unknown | ((positional_only=Unknown, /) -> Unknown), /) -> Unknown + # revealed: (positional_only=Unknown | Divergent, /) -> Unknown reveal_type(self.c) - # revealed: (*, kw_only=Unknown | ((*, kw_only=Unknown) -> Unknown)) -> Unknown + # revealed: (*, kw_only=Unknown | Divergent) -> Unknown reveal_type(self.d) ``` diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md index 75c76d5d022a95..81c1960de49817 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/paramspec.md @@ -417,16 +417,13 @@ The `converter` function act as a decorator here: def f3(x: int, y: str) -> int: return 1 -# TODO: This should reveal `(x: int, y: str) -> bool` but there's a cycle: https://github.com/astral-sh/ty/issues/1729 -reveal_type(f3) # revealed: ((x: int, y: str) -> bool) | ((x: Divergent, y: Divergent) -> bool) +reveal_type(f3) # revealed: (x: int, y: str) -> bool reveal_type(f3(1, "a")) # revealed: bool reveal_type(f3(x=1, y="a")) # revealed: bool reveal_type(f3(1, y="a")) # revealed: bool reveal_type(f3(y="a", x=1)) # revealed: bool -# TODO: There should only be one error but the type of `f3` is a union: https://github.com/astral-sh/ty/issues/1729 -# error: [missing-argument] "No argument provided for required parameter `y`" # error: [missing-argument] "No argument provided for required parameter `y`" f3(1) # error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `Literal["a"]`" diff --git a/crates/ty_python_semantic/src/semantic_index.rs b/crates/ty_python_semantic/src/semantic_index.rs index f4ab765f08b2b2..cbb5e9229f0530 100644 --- a/crates/ty_python_semantic/src/semantic_index.rs +++ b/crates/ty_python_semantic/src/semantic_index.rs @@ -525,6 +525,11 @@ impl<'db> SemanticIndex<'db> { self.scopes_by_node[&node.node_key()] } + #[track_caller] + pub(crate) fn try_node_scope(&self, node: NodeWithScopeRef) -> Option { + self.scopes_by_node.get(&node.node_key()).copied() + } + /// Checks if there is an import of `__future__.annotations` in the global scope, which affects /// the logic for type inference. pub(super) fn has_future_annotations(&self) -> bool { diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index dae46bca03e1df..d2d87e3ad1a192 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -73,17 +73,18 @@ use crate::types::diagnostic::{ report_runtime_check_against_non_runtime_checkable_protocol, }; use crate::types::display::DisplaySettings; -use crate::types::generics::{GenericContext, InferableTypeVars}; +use crate::types::generics::{GenericContext, InferableTypeVars, typing_self}; +use crate::types::infer::nearest_enclosing_class; use crate::types::list_members::all_members; use crate::types::narrow::ClassInfoConstraintFunction; -use crate::types::signatures::{CallableSignature, Signature}; +use crate::types::signatures::{CallableSignature, Parameter, Signature}; use crate::types::visitor::any_over_type; use crate::types::{ ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, CallableTypeKind, ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType, NormalizedVisitor, SpecialFormType, Truthiness, Type, TypeContext, TypeMapping, TypeRelation, - UnionBuilder, binding_type, definition_expression_type, walk_signature, + UnionBuilder, binding_type, definition_expression_type, infer_definition_types, walk_signature, }; use crate::{Db, FxOrderSet, ModuleName, resolve_module}; @@ -195,6 +196,12 @@ pub(crate) fn is_implicit_classmethod(function_name: &str) -> bool { matches!(function_name, "__init_subclass__" | "__class_getitem__") } +#[derive(Clone, Debug, Eq, Hash, PartialEq, get_size2::GetSize, salsa::Update)] +pub struct InferredFunctionSignature<'db> { + pub(crate) parameters: Vec>, + pub(crate) return_type: Option>, +} + /// Representation of a function definition in the AST: either a non-generic function, or a generic /// function that has not been specialized. /// @@ -227,6 +234,11 @@ pub struct OverloadLiteral<'db> { /// The arguments to `dataclass_transformer`, if this function was annotated /// with `@dataclass_transformer(...)`. pub(crate) dataclass_transformer_params: Option>, + + inferred_signature: Option>, + + #[returns(ref)] + inferred_defaults: Vec>>, } // The Salsa heap is tracked separately. @@ -247,6 +259,8 @@ impl<'db> OverloadLiteral<'db> { self.decorators(db), self.deprecated(db), Some(params), + self.inferred_signature(db), + self.inferred_defaults(db), ) } @@ -499,13 +513,68 @@ impl<'db> OverloadLiteral<'db> { index, ); - Signature::from_function( + let mut raw_signature = Signature::from_function( db, pep695_ctx, definition, function_stmt_node, + self.inferred_signature(db), + self.inferred_defaults(db), has_implicitly_positional_first_parameter, - ) + ); + + let generic_context = raw_signature.generic_context; + raw_signature.add_implicit_self_annotation(db, || { + if self.is_staticmethod(db) || self.is_classmethod(db) { + return None; + } + + let method_may_be_generic = generic_context + .is_some_and(|context| context.variables(db).any(|v| v.typevar(db).is_self(db))); + + let class_scope_id = definition.scope(db); + let class_scope = index.scope(class_scope_id.file_scope_id(db)); + let class_node = class_scope.node().as_class()?; + let class_def = index.expect_single_definition(class_node); + let (class_literal, class_is_generic) = match infer_definition_types(db, class_def) + .declaration_type(class_def) + .inner_type() + { + Type::ClassLiteral(class_literal) => { + (class_literal, class_literal.generic_context(db).is_some()) + } + Type::GenericAlias(alias) => (alias.origin(db), true), + _ => return None, + }; + + if method_may_be_generic + || class_is_generic + || class_literal + .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( + typing_self(db, scope_id, typevar_binding_context, class) + .map(Type::TypeVar) + .expect( + "We should always find the surrounding class \ + for an implicit self: Self annotation", + ), + ) + } 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(class_literal.to_non_generic_instance(db)) + } + }); + + raw_signature } pub(crate) fn parameter_span( @@ -1114,19 +1183,17 @@ impl<'db> FunctionType<'db> { nested: bool, ) -> Option { let literal = self.literal(db); - let updated_signature = match self.updated_signature(db) { - Some(signature) => Some(signature.recursive_type_normalized_impl(db, div, nested)?), - None => None, - }; - let updated_last_definition_signature = match self.updated_last_definition_signature(db) { - Some(signature) => Some(signature.recursive_type_normalized_impl(db, div, nested)?), - None => None, - }; + let updated_signature = self + .signature(db) + .recursive_type_normalized_impl(db, div, nested)?; + let updated_last_definition_signature = self + .last_definition_signature(db) + .recursive_type_normalized_impl(db, div, nested)?; Some(Self::new( db, literal, - updated_signature, - updated_last_definition_signature, + Some(updated_signature), + Some(updated_last_definition_signature), )) } } diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index b9adc93eb28ff1..366026d7df14cd 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -49,7 +49,7 @@ use crate::semantic_index::expression::Expression; use crate::semantic_index::scope::ScopeId; use crate::semantic_index::{SemanticIndex, semantic_index}; use crate::types::diagnostic::TypeCheckDiagnostics; -use crate::types::function::FunctionType; +use crate::types::function::{FunctionType, InferredFunctionSignature}; use crate::types::generics::Specialization; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ @@ -546,6 +546,9 @@ struct ScopeInferenceExtra<'db> { /// The diagnostics for this region. diagnostics: TypeCheckDiagnostics, + + /// The inferred signature of a function definition (without any default values filled in). + signature: Option>, } impl<'db> ScopeInference<'db> { @@ -605,6 +608,12 @@ impl<'db> ScopeInference<'db> { extra.string_annotations.contains(&expression.into()) } + + pub(crate) fn inferred_function_signature(&self) -> Option> { + self.extra + .as_ref() + .and_then(|extra| extra.signature.clone()) + } } /// The inferred types for a definition region. @@ -650,6 +659,9 @@ struct DefinitionInferenceExtra<'db> { /// For function definitions, the undecorated type of the function. undecorated_type: Option>, + + /// The inferred signature of a function definition (without any default values filled in). + signature: Option>, } impl<'db> DefinitionInference<'db> { @@ -778,6 +790,12 @@ impl<'db> DefinitionInference<'db> { pub(crate) fn undecorated_type(&self) -> Option> { self.extra.as_ref().and_then(|extra| extra.undecorated_type) } + + pub(crate) fn inferred_function_signature(&self) -> Option> { + self.extra + .as_ref() + .and_then(|extra| extra.signature.clone()) + } } /// The inferred types for an expression region. diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index 06cb805c15a376..4171857428cb8e 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -83,8 +83,8 @@ use crate::types::diagnostic::{ report_rebound_typevar, report_slice_step_size_zero, report_unsupported_comparison, }; use crate::types::function::{ - FunctionDecorators, FunctionLiteral, FunctionType, KnownFunction, OverloadLiteral, - is_implicit_classmethod, is_implicit_staticmethod, + FunctionDecorators, FunctionLiteral, FunctionType, InferredFunctionSignature, KnownFunction, + OverloadLiteral, is_implicit_classmethod, is_implicit_staticmethod, }; use crate::types::generics::{ GenericContext, InferableTypeVars, LegacyGenericBase, SpecializationBuilder, bind_typevar, @@ -308,6 +308,9 @@ pub(super) struct TypeInferenceBuilder<'db, 'ast> { /// A list of `dataclass_transform` field specifiers that are "active" (when inferring /// the right hand side of an annotated assignment in a class that is a dataclass). dataclass_field_specifiers: SmallVec<[Type<'db>; NUM_FIELD_SPECIFIERS_INLINE]>, + + /// The inferred signature of a function definition (without any default values filled in). + signature: Option>, } impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { @@ -346,6 +349,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { cycle_recovery: None, all_definitely_bound: true, dataclass_field_specifiers: SmallVec::new(), + signature: None, } } @@ -1910,12 +1914,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let binding_context = self.index.expect_single_definition(function); let previous_typevar_binding_context = self.typevar_binding_context.replace(binding_context); - self.infer_return_type_annotation( + let return_type = self.infer_return_type_annotation( function.returns.as_deref(), self.defer_annotations().into(), ); self.infer_type_parameters(type_params); - self.infer_parameters(&function.parameters); + let parameters = self.infer_parameters(&function.parameters); + self.signature = Some(InferredFunctionSignature { + parameters, + return_type, + }); self.typevar_binding_context = previous_typevar_binding_context; } @@ -2281,29 +2289,40 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { decorator_types_and_nodes.push((decorator_type, decorator)); } - for default in parameters + let defaults: Vec<_> = parameters .iter_non_variadic_params() - .filter_map(|param| param.default.as_deref()) - { - self.infer_expression(default, TypeContext::default()); - } + .map(|param| { + param + .default + .as_deref() + .map(|default| self.infer_expression(default, TypeContext::default())) + }) + .collect(); // If there are type params, parameters and returns are evaluated in that scope, that is, in // `infer_function_type_params`, rather than here. - if type_params.is_none() { + let inferred_signature = if type_params.is_none() { if self.defer_annotations() { self.deferred.insert(definition, self.multi_inference_state); + None } else { let previous_typevar_binding_context = self.typevar_binding_context.replace(definition); - self.infer_return_type_annotation( + let return_type = self.infer_return_type_annotation( returns.as_deref(), DeferredExpressionState::None, ); - self.infer_parameters(parameters); + let parameters = self.infer_parameters(parameters); + self.signature = Some(InferredFunctionSignature { + parameters, + return_type, + }); self.typevar_binding_context = previous_typevar_binding_context; + self.signature.clone() } - } + } else { + None + }; let known_function = KnownFunction::try_from_definition_and_name(self.db(), definition, name); @@ -2326,6 +2345,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { function_decorators, deprecated, dataclass_transformer_params, + inferred_signature, + defaults, ); let function_literal = FunctionLiteral::new(self.db(), overload_literal); @@ -2423,8 +2444,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &mut self, returns: Option<&ast::Expr>, deferred_expression_state: DeferredExpressionState, - ) { - if let Some(returns) = returns { + ) -> Option> { + returns.map(|returns| { let annotated = self.infer_annotation_expression(returns, deferred_expression_state); if !annotated.qualifiers.is_empty() { @@ -2444,32 +2465,71 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } } - } + + annotated.inner_type() + }) } - fn infer_parameters(&mut self, parameters: &ast::Parameters) { + fn infer_parameters(&mut self, parameters: &ast::Parameters) -> Vec> { let ast::Parameters { range: _, node_index: _, - posonlyargs: _, - args: _, + posonlyargs, + args, vararg, - kwonlyargs: _, + kwonlyargs, kwarg, } = parameters; - for param_with_default in parameters.iter_non_variadic_params() { - self.infer_parameter_with_default(param_with_default); + let mut parameters = Vec::new(); + + for positional_only in posonlyargs { + let parameter_name = positional_only.parameter.name.id.clone(); + let parameter_type = self.infer_parameter_with_default(positional_only); + let parameter = Parameter::positional_only(Some(parameter_name)) + .with_optional_annotated_type(parameter_type); + parameters.push(parameter); + } + + for positional_or_keyword in args { + let parameter_name = positional_or_keyword.parameter.name.id.clone(); + let parameter_type = self.infer_parameter_with_default(positional_or_keyword); + let parameter = Parameter::positional_or_keyword(parameter_name) + .with_optional_annotated_type(parameter_type); + parameters.push(parameter); } + if let Some(vararg) = vararg { - self.infer_parameter(vararg); + let parameter_name = vararg.name.id.clone(); + let parameter_type = self.infer_parameter(vararg); + let parameter = + Parameter::variadic(parameter_name).with_optional_annotated_type(parameter_type); + parameters.push(parameter); } + + for keyword_only in kwonlyargs { + let parameter_name = keyword_only.parameter.name.id.clone(); + let parameter_type = self.infer_parameter_with_default(keyword_only); + let parameter = Parameter::keyword_only(parameter_name) + .with_optional_annotated_type(parameter_type); + parameters.push(parameter); + } + if let Some(kwarg) = kwarg { - self.infer_parameter(kwarg); + let parameter_name = kwarg.name.id.clone(); + let parameter_type = self.infer_parameter(kwarg); + let parameter = Parameter::keyword_variadic(parameter_name) + .with_optional_annotated_type(parameter_type); + parameters.push(parameter); } + + parameters } - fn infer_parameter_with_default(&mut self, parameter_with_default: &ast::ParameterWithDefault) { + fn infer_parameter_with_default( + &mut self, + parameter_with_default: &ast::ParameterWithDefault, + ) -> Option> { let ast::ParameterWithDefault { range: _, node_index: _, @@ -2502,9 +2562,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } } + + annotated.map(|annotated| annotated.inner_type()) } - fn infer_parameter(&mut self, parameter: &ast::Parameter) { + fn infer_parameter(&mut self, parameter: &ast::Parameter) -> Option> { let ast::Parameter { range: _, node_index: _, @@ -2512,10 +2574,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { annotation, } = parameter; - self.infer_optional_annotation_expression( + let annotated = self.infer_optional_annotation_expression( annotation.as_deref(), self.defer_annotations().into(), ); + + annotated.map(|annotated| annotated.inner_type()) } /// Set initial declared type (if annotated) and inferred type for a function-parameter symbol, @@ -2947,11 +3011,15 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { function: &ast::StmtFunctionDef, ) { let previous_typevar_binding_context = self.typevar_binding_context.replace(definition); - self.infer_return_type_annotation( + let return_type = self.infer_return_type_annotation( function.returns.as_deref(), DeferredExpressionState::Deferred, ); - self.infer_parameters(function.parameters.as_ref()); + let parameters = self.infer_parameters(function.parameters.as_ref()); + self.signature = Some(InferredFunctionSignature { + parameters, + return_type, + }); self.typevar_binding_context = previous_typevar_binding_context; } @@ -8896,10 +8964,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let place = if let Some(place_id) = place_table.place_id(expr) { place_from_bindings(db, use_def.all_reachable_bindings(place_id)).place } else { - assert!( - self.deferred_state.in_string_annotation(), - "Expected the place table to create a place for every valid PlaceExpr node" - ); Place::Undefined }; (place, None) @@ -12383,6 +12447,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { // Ignored; only relevant to definition regions undecorated_type: _, + signature: _, // builder only state typevar_binding_context: _, @@ -12449,6 +12514,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { deferred, cycle_recovery, undecorated_type, + signature, // builder only state dataclass_field_specifiers: _, all_definitely_bound: _, @@ -12469,7 +12535,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { || !string_annotations.is_empty() || cycle_recovery.is_some() || undecorated_type.is_some() - || !deferred.is_empty()) + || !deferred.is_empty() + || signature.is_some()) .then(|| { Box::new(DefinitionInferenceExtra { string_annotations, @@ -12477,6 +12544,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { deferred: deferred.into_boxed_slice(), diagnostics, undecorated_type, + signature, }) }); @@ -12517,6 +12585,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { mut expressions, scope, cycle_recovery, + signature, // Ignored, because scope types are never extended into other scopes. deferred: _, @@ -12542,15 +12611,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let _ = scope; let diagnostics = context.finish(); - let extra = - (!string_annotations.is_empty() || !diagnostics.is_empty() || cycle_recovery.is_some()) - .then(|| { - Box::new(ScopeInferenceExtra { - string_annotations, - cycle_recovery, - diagnostics, - }) - }); + let extra = (!string_annotations.is_empty() + || !diagnostics.is_empty() + || cycle_recovery.is_some() + || signature.is_some()) + .then(|| { + Box::new(ScopeInferenceExtra { + string_annotations, + cycle_recovery, + diagnostics, + signature, + }) + }); expressions.shrink_to_fit(); diff --git a/crates/ty_python_semantic/src/types/signatures.rs b/crates/ty_python_semantic/src/types/signatures.rs index 028087ceffe07a..311d7e0d7eccb0 100644 --- a/crates/ty_python_semantic/src/types/signatures.rs +++ b/crates/ty_python_semantic/src/types/signatures.rs @@ -12,110 +12,26 @@ use std::{collections::HashMap, slice::Iter}; -use itertools::{EitherOrBoth, Itertools}; -use ruff_db::parsed::parsed_module; -use ruff_python_ast::ParameterWithDefault; +use itertools::EitherOrBoth; use smallvec::{SmallVec, smallvec_inline}; -use super::{ - DynamicType, Type, TypeVarVariance, definition_expression_type, infer_definition_types, - semantic_index, -}; -use crate::semantic_index::definition::{Definition, DefinitionKind}; +use super::{DynamicType, Type, TypeVarVariance}; +use crate::semantic_index::definition::Definition; +use crate::semantic_index::scope::NodeWithScopeRef; +use crate::semantic_index::semantic_index; use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension}; -use crate::types::function::{is_implicit_classmethod, is_implicit_staticmethod}; -use crate::types::generics::{ - GenericContext, InferableTypeVars, typing_self, walk_generic_context, -}; -use crate::types::infer::nearest_enclosing_class; +use crate::types::function::InferredFunctionSignature; +use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_context}; use crate::types::{ - ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind, ClassLiteral, + ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind, FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, TypeContext, - TypeMapping, TypeRelation, VarianceInferable, todo_type, + TypeMapping, TypeRelation, VarianceInferable, infer_deferred_types, infer_scope_types, + todo_type, }; use crate::{Db, FxOrderSet}; use ruff_python_ast::{self as ast, name::Name}; -#[derive(Clone, Copy, Debug)] -#[expect(clippy::struct_excessive_bools)] -struct MethodInformation<'db> { - is_staticmethod: bool, - is_classmethod: bool, - method_may_be_generic: bool, - class_literal: ClassLiteral<'db>, - class_is_generic: bool, -} - -fn infer_method_information<'db>( - db: &'db dyn Db, - definition: Definition<'db>, -) -> Option> { - let DefinitionKind::Function(function_definition) = definition.kind(db) else { - return None; - }; - - let class_scope_id = definition.scope(db); - let file = class_scope_id.file(db); - let module = parsed_module(db, file).load(db); - let index = semantic_index(db, file); - - let class_scope = index.scope(class_scope_id.file_scope_id(db)); - let class_node = class_scope.node().as_class()?; - - let function_node = function_definition.node(&module); - let function_name = &function_node.name; - - let mut is_staticmethod = is_implicit_classmethod(function_name); - let mut is_classmethod = is_implicit_staticmethod(function_name); - - let inference = infer_definition_types(db, definition); - for decorator in &function_node.decorator_list { - let decorator_ty = inference.expression_type(&decorator.expression); - - match decorator_ty - .as_class_literal() - .and_then(|class| class.known(db)) - { - Some(KnownClass::Staticmethod) => { - is_staticmethod = true; - } - Some(KnownClass::Classmethod) => { - is_classmethod = true; - } - _ => {} - } - } - - let method_may_be_generic = match inference.declaration_type(definition).inner_type() { - Type::FunctionLiteral(f) => f.signature(db).overloads.iter().any(|s| { - s.generic_context - .is_some_and(|context| context.variables(db).any(|v| v.typevar(db).is_self(db))) - }), - _ => true, - }; - - let class_def = index.expect_single_definition(class_node); - let (class_literal, class_is_generic) = match infer_definition_types(db, class_def) - .declaration_type(class_def) - .inner_type() - { - Type::ClassLiteral(class_literal) => { - (class_literal, class_literal.generic_context(db).is_some()) - } - Type::GenericAlias(alias) => (alias.origin(db), true), - _ => return None, - }; - - Some(MethodInformation { - is_staticmethod, - is_classmethod, - method_may_be_generic, - class_literal, - class_is_generic, - }) -} - /// 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)] @@ -604,20 +520,43 @@ impl<'db> Signature<'db> { pep695_generic_context: Option>, definition: Definition<'db>, function_node: &ast::StmtFunctionDef, + inferred_signature: Option>, + defaults: &[Option>], has_implicitly_positional_first_parameter: bool, ) -> Self { - let parameters = Parameters::from_parameters( + let inferred_signature = inferred_signature + .or_else(|| { + let file = definition.file(db); + let index = semantic_index(db, file); + let file_scope = index + .try_node_scope(NodeWithScopeRef::FunctionTypeParameters(function_node))?; + let scope = file_scope.to_scope_id(db, file); + infer_scope_types(db, scope).inferred_function_signature() + }) + .or_else(|| infer_deferred_types(db, definition).inferred_function_signature()); + let Some(mut inferred_signature) = inferred_signature else { + return Self::bottom(); + }; + + let mut defaults = defaults.iter(); + for parameter in &mut inferred_signature.parameters { + parameter.update_default_type(|| { + defaults + .next() + .expect("should have optional default for each non-variadic parameter") + .map(|ty| ty.replace_parameter_defaults(db)) + }); + } + let mut parameters = Parameters::new(db, inferred_signature.parameters); + parameters + .find_pep484_positional_only_parameters(has_implicitly_positional_first_parameter); + + let legacy_generic_context = GenericContext::from_function_params( db, definition, - function_node.parameters.as_ref(), - has_implicitly_positional_first_parameter, + ¶meters, + inferred_signature.return_type, ); - let return_ty = function_node - .returns - .as_ref() - .map(|returns| definition_expression_type(db, definition, returns.as_ref())); - let legacy_generic_context = - GenericContext::from_function_params(db, definition, ¶meters, return_ty); let full_generic_context = GenericContext::merge_pep695_and_legacy( db, pep695_generic_context, @@ -628,7 +567,7 @@ impl<'db> Signature<'db> { generic_context: full_generic_context, definition: Some(definition), parameters, - return_ty, + return_ty: inferred_signature.return_type, } } @@ -710,7 +649,7 @@ impl<'db> Signature<'db> { let parameters = { let mut parameters = Vec::with_capacity(self.parameters.len()); for param in &self.parameters { - parameters.push(param.recursive_type_normalized_impl(db, div, nested)?); + parameters.push(param.recursive_type_normalized_impl(db, div, true)?); } Parameters::new(db, parameters) }; @@ -771,6 +710,51 @@ impl<'db> Signature<'db> { &self.parameters } + /// Adds an implicit annotation to the first parameter of this signature, if that parameter is + /// positional and does not already have an annotation. We do not check whether that's the + /// right thing to do! The caller must determine whether the first parameter is actually a + /// `self` or `cls` parameter, and must determine the correct type to use as the implicit + /// annotation. + pub(crate) fn add_implicit_self_annotation( + &mut self, + db: &'db dyn Db, + self_type: impl FnOnce() -> Option>, + ) { + if let Some(first_parameter) = self.parameters.value.first_mut() + && first_parameter.is_positional() + && first_parameter.annotated_type.is_none() + && let Some(self_type) = self_type() + { + first_parameter.annotated_type = Some(self_type); + first_parameter.inferred_annotation = true; + + // If we've added an implicit `self` annotation, we might need to update the + // signature's generic context, too. (The generic context should include any synthetic + // typevars created for `typing.Self`, even if the `typing.Self` annotation was added + // implicitly.) + if let Type::TypeVar(self_typevar) = self_type { + match self.generic_context.as_mut() { + Some(generic_context) + if generic_context + .binds_typevar(db, self_typevar.typevar(db)) + .is_some() => {} + Some(generic_context) => { + *generic_context = GenericContext::from_typevar_instances( + db, + std::iter::once(self_typevar).chain(generic_context.variables(db)), + ); + } + None => { + self.generic_context = Some(GenericContext::from_typevar_instances( + db, + std::iter::once(self_typevar), + )); + } + } + } + } + } + /// Return the definition associated with this signature, if any. pub(crate) fn definition(&self) -> Option> { self.definition @@ -1652,181 +1636,36 @@ impl<'db> Parameters<'db> { None } - fn from_parameters( - db: &'db dyn Db, - definition: Definition<'db>, - parameters: &ast::Parameters, + pub(crate) fn find_pep484_positional_only_parameters( + &mut self, has_implicitly_positional_first_parameter: bool, - ) -> Self { - let ast::Parameters { - posonlyargs, - args, - vararg, - kwonlyargs, - kwarg, - range: _, - node_index: _, - } = parameters; - - let default_type = |param: &ast::ParameterWithDefault| { - param.default().map(|default| { - definition_expression_type(db, definition, default).replace_parameter_defaults(db) - }) - }; - - let method_info = infer_method_information(db, definition); - let is_static_or_classmethod = - method_info.is_some_and(|f| f.is_staticmethod || f.is_classmethod); - - let inferred_annotation = |arg: &ParameterWithDefault| { - if let Some(MethodInformation { - method_may_be_generic, - class_literal, - class_is_generic, - .. - }) = method_info - && !is_static_or_classmethod - && arg.parameter.annotation().is_none() - && parameters.index(arg.name().id()) == Some(0) - { - if method_may_be_generic - || class_is_generic - || class_literal - .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( - typing_self(db, scope_id, typevar_binding_context, class) - .map(Type::TypeVar) - .expect("We should always find the surrounding class for an implicit self: Self annotation"), - ) - } 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(class_literal.to_non_generic_instance(db)) - } - } else { - None - } - }; - - let pos_only_param = |param: &ast::ParameterWithDefault| { - if let Some(inferred_annotation_type) = inferred_annotation(param) { - 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(); - - let mut pos_or_keyword_iter = args.iter(); - - // If there are no PEP-570 positional-only parameters, check for the legacy PEP-484 convention - // for denoting positional-only parameters (parameters that start with `__` and do not end with `__`) - if positional_only.is_empty() { - let pos_or_keyword_iter = pos_or_keyword_iter.by_ref(); - - if has_implicitly_positional_first_parameter { - positional_only.extend(pos_or_keyword_iter.next().map(pos_only_param)); - } - - positional_only.extend( - pos_or_keyword_iter - .peeking_take_while(|param| param.uses_pep_484_positional_only_convention()) - .map(pos_only_param), - ); + ) { + // If there are any PEP-570 positional-only parameters, those take precedence, and PEP-484 + // isn't relevant. + if self.value.iter().any(Parameter::is_positional_only) { + return; + } + + // Otherwise we want to convert some of the initial positional-or-keyword parameters to + // become positional-only. An initial `self` and `cls` parameter might be converted + // (controlled by `has_implicitly_positional_first_parameter`), and any other leading + // parameters with PEP-484-compatible names are converted. + let mut positional_or_keyword_parameters = self + .value + .iter_mut() + .take_while(|param| param.is_positional_or_keyword()); + + if has_implicitly_positional_first_parameter + && let Some(param) = positional_or_keyword_parameters.next() + { + param.convert_positional_or_keyword_to_positional_only(); } - let positional_or_keyword = pos_or_keyword_iter.map(|arg| { - if let Some(inferred_annotation_type) = inferred_annotation(arg) { - Parameter { - annotated_type: Some(inferred_annotation_type), - inferred_annotation: true, - kind: ParameterKind::PositionalOrKeyword { - name: arg.parameter.name.id.clone(), - default_type: default_type(arg), - }, - form: ParameterForm::Value, - } - } else { - Parameter::from_node_and_kind( - db, - definition, - &arg.parameter, - ParameterKind::PositionalOrKeyword { - name: arg.parameter.name.id.clone(), - default_type: default_type(arg), - }, - ) - } - }); - - let variadic = vararg.as_ref().map(|arg| { - Parameter::from_node_and_kind( - db, - definition, - arg, - ParameterKind::Variadic { - name: arg.name.id.clone(), - }, - ) - }); - - let keyword_only = kwonlyargs.iter().map(|arg| { - Parameter::from_node_and_kind( - db, - definition, - &arg.parameter, - ParameterKind::KeywordOnly { - name: arg.parameter.name.id.clone(), - default_type: default_type(arg), - }, - ) - }); - - let keywords = kwarg.as_ref().map(|arg| { - Parameter::from_node_and_kind( - db, - definition, - arg, - ParameterKind::KeywordVariadic { - name: arg.name.id.clone(), - }, - ) - }); - - Self::new( - db, - positional_only - .into_iter() - .chain(positional_or_keyword) - .chain(variadic) - .chain(keyword_only) - .chain(keywords), - ) + let pep484_parameters = positional_or_keyword_parameters + .take_while(|param| param.uses_pep_484_positional_only_convention()); + for param in pep484_parameters { + param.convert_positional_or_keyword_to_positional_only(); + } } fn apply_type_mapping_impl<'a>( @@ -2013,8 +1852,15 @@ impl<'db> Parameter<'db> { } } - pub(crate) fn with_annotated_type(mut self, annotated_type: Type<'db>) -> Self { - self.annotated_type = Some(annotated_type); + pub(crate) fn with_annotated_type(self, annotated_type: Type<'db>) -> Self { + self.with_optional_annotated_type(Some(annotated_type)) + } + + pub(crate) fn with_optional_annotated_type( + mut self, + annotated_type: Option>, + ) -> Self { + self.annotated_type = annotated_type; self } @@ -2030,6 +1876,15 @@ impl<'db> Parameter<'db> { self } + pub(crate) fn update_default_type(&mut self, default: impl FnOnce() -> Option>) { + match &mut self.kind { + ParameterKind::PositionalOnly { default_type, .. } + | ParameterKind::PositionalOrKeyword { default_type, .. } + | ParameterKind::KeywordOnly { default_type, .. } => *default_type = default(), + ParameterKind::Variadic { .. } | ParameterKind::KeywordVariadic { .. } => {} + } + } + pub(crate) fn type_form(mut self) -> Self { self.form = ParameterForm::Type; self @@ -2189,22 +2044,6 @@ impl<'db> Parameter<'db> { }) } - fn from_node_and_kind( - db: &'db dyn Db, - definition: Definition<'db>, - parameter: &ast::Parameter, - kind: ParameterKind<'db>, - ) -> Self { - Self { - annotated_type: parameter - .annotation() - .map(|annotation| definition_expression_type(db, definition, annotation)), - kind, - form: ParameterForm::Value, - inferred_annotation: false, - } - } - /// Returns `true` if this is a keyword-only parameter. pub(crate) fn is_keyword_only(&self) -> bool { matches!(self.kind, ParameterKind::KeywordOnly { .. }) @@ -2215,6 +2054,11 @@ impl<'db> Parameter<'db> { matches!(self.kind, ParameterKind::PositionalOnly { .. }) } + /// Returns `true` if this is a positional-or-keyword parameter. + pub(crate) fn is_positional_or_keyword(&self) -> bool { + matches!(self.kind, ParameterKind::PositionalOrKeyword { .. }) + } + /// Returns `true` if this is a variadic parameter. pub(crate) fn is_variadic(&self) -> bool { matches!(self.kind, ParameterKind::Variadic { .. }) @@ -2272,6 +2116,24 @@ impl<'db> Parameter<'db> { } } + /// Return `true` if the parameter name uses the pre-PEP-570 convention + /// (specified in PEP 484) to indicate to a type checker that it should be treated + /// as positional-only. + fn uses_pep_484_positional_only_convention(&self) -> bool { + self.name() + .is_some_and(|name| name.starts_with("__") && !name.ends_with("__")) + } + + fn convert_positional_or_keyword_to_positional_only(&mut self) { + let ParameterKind::PositionalOrKeyword { name, default_type } = &self.kind else { + unreachable!("we should be limited to positional-or-keyword parameters"); + }; + self.kind = ParameterKind::PositionalOnly { + name: Some(name.clone()), + default_type: *default_type, + }; + } + /// Display name of the parameter, if it has one. pub(crate) fn display_name(&self) -> Option { self.name().map(|name| match self.kind {