mirror of https://github.com/astral-sh/ruff
[ty] Defer all parameter and return type annotations (#21906)
As described in astral-sh/ty#1729, we previously had a salsa cycle when inferring the signature of many function definitions. The most obvious case happened when (a) the function was decorated, (b) it had no PEP-695 type params, and (c) annotations were not always deferred (e.g. in a stub file). We currently evaluate and apply function decorators eagerly, as part of `infer_function_definition`. Applying a decorator requires knowing the signature of the function being decorated. There were two places where signature construction called `infer_definition_types` cyclically. The simpler case was that we were looking up the generic context and decorator list of the function to determine whether it has an implicit `self` parameter. Before, we used `infer_definition_types` to determine that information. But since we're in the middle of signature construction for the function, we can just thread the information through directly. The harder case is that signature construction requires knowing the inferred parameter and return type annotations. When (b) and (c) hold, those type annotations are inferred in `infer_function_definition`! (In theory, we've already finished that by the time we start applying decorators, but signature construction doesn't know that.) If annotations are deferred, the params/return annotations are inferred in `infer_deferred_types`; if there are PEP-695 type params, they're inferred in `infer_function_type_params`. Both of those are different salsa queries, and don't induce this cycle. So the quick fix here is to always defer inference of the function params/return, so that they are always inferred under a different salsa query. A more principled fix would be to apply decorators lazily, just like we construct signatures lazily. But that is a more invasive fix. Fixes astral-sh/ty#1729 --------- Co-authored-by: Alex Waygood <alex.waygood@gmail.com>
This commit is contained in:
parent
d442433e93
commit
c8851ecf70
|
|
@ -282,8 +282,10 @@ reveal_type(C().method()) # revealed: C
|
||||||
|
|
||||||
## Class Methods
|
## Class Methods
|
||||||
|
|
||||||
|
### Explicit
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing import Self, TypeVar
|
from typing import Self
|
||||||
|
|
||||||
class Shape:
|
class Shape:
|
||||||
def foo(self: Self) -> Self:
|
def foo(self: Self) -> Self:
|
||||||
|
|
@ -298,6 +300,64 @@ class Circle(Shape): ...
|
||||||
|
|
||||||
reveal_type(Shape().foo()) # revealed: Shape
|
reveal_type(Shape().foo()) # revealed: Shape
|
||||||
reveal_type(Shape.bar()) # revealed: Shape
|
reveal_type(Shape.bar()) # revealed: Shape
|
||||||
|
|
||||||
|
reveal_type(Circle().foo()) # revealed: Circle
|
||||||
|
reveal_type(Circle.bar()) # revealed: Circle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implicit
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
class Shape:
|
||||||
|
def foo(self) -> Self:
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def bar(cls) -> Self:
|
||||||
|
reveal_type(cls) # revealed: type[Self@bar]
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
class Circle(Shape): ...
|
||||||
|
|
||||||
|
reveal_type(Shape().foo()) # revealed: Shape
|
||||||
|
reveal_type(Shape.bar()) # revealed: Shape
|
||||||
|
|
||||||
|
reveal_type(Circle().foo()) # revealed: Circle
|
||||||
|
reveal_type(Circle.bar()) # revealed: Circle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implicit in generic class
|
||||||
|
|
||||||
|
```py
|
||||||
|
from typing import Self
|
||||||
|
|
||||||
|
class GenericShape[T]:
|
||||||
|
def foo(self) -> Self:
|
||||||
|
return self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def bar(cls) -> Self:
|
||||||
|
reveal_type(cls) # revealed: type[Self@bar]
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def baz[U](cls, u: U) -> "GenericShape[U]":
|
||||||
|
reveal_type(cls) # revealed: type[Self@baz]
|
||||||
|
return cls()
|
||||||
|
|
||||||
|
class GenericCircle[T](GenericShape[T]): ...
|
||||||
|
|
||||||
|
reveal_type(GenericShape().foo()) # revealed: GenericShape[Unknown]
|
||||||
|
reveal_type(GenericShape.bar()) # revealed: GenericShape[Unknown]
|
||||||
|
reveal_type(GenericShape[int].bar()) # revealed: GenericShape[int]
|
||||||
|
reveal_type(GenericShape.baz(1)) # revealed: GenericShape[Literal[1]]
|
||||||
|
|
||||||
|
reveal_type(GenericCircle().foo()) # revealed: GenericCircle[Unknown]
|
||||||
|
reveal_type(GenericCircle.bar()) # revealed: GenericCircle[Unknown]
|
||||||
|
reveal_type(GenericCircle[int].bar()) # revealed: GenericCircle[int]
|
||||||
|
reveal_type(GenericCircle.baz(1)) # revealed: GenericShape[Literal[1]]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
|
||||||
|
|
@ -417,16 +417,13 @@ The `converter` function act as a decorator here:
|
||||||
def f3(x: int, y: str) -> int:
|
def f3(x: int, y: str) -> int:
|
||||||
return 1
|
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
|
||||||
reveal_type(f3) # revealed: ((x: int, y: str) -> bool) | ((x: Divergent, y: Divergent) -> bool)
|
|
||||||
|
|
||||||
reveal_type(f3(1, "a")) # revealed: bool
|
reveal_type(f3(1, "a")) # revealed: bool
|
||||||
reveal_type(f3(x=1, y="a")) # revealed: bool
|
reveal_type(f3(x=1, y="a")) # revealed: bool
|
||||||
reveal_type(f3(1, y="a")) # revealed: bool
|
reveal_type(f3(1, y="a")) # revealed: bool
|
||||||
reveal_type(f3(y="a", x=1)) # 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`"
|
# error: [missing-argument] "No argument provided for required parameter `y`"
|
||||||
f3(1)
|
f3(1)
|
||||||
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `Literal["a"]`"
|
# error: [invalid-argument-type] "Argument is incorrect: Expected `int`, found `Literal["a"]`"
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,8 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
|
||||||
std::mem::replace(&mut self.multi_inference, multi_inference)
|
std::mem::replace(&mut self.multi_inference, multi_inference)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn set_in_no_type_check(&mut self, no_type_check: InNoTypeCheck) {
|
pub(super) fn set_in_no_type_check(&mut self, no_type_check: InNoTypeCheck) -> InNoTypeCheck {
|
||||||
self.no_type_check = no_type_check;
|
std::mem::replace(&mut self.no_type_check, no_type_check)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_in_no_type_check(&self) -> bool {
|
fn is_in_no_type_check(&self) -> bool {
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,8 @@ use crate::types::diagnostic::{
|
||||||
report_runtime_check_against_non_runtime_checkable_protocol,
|
report_runtime_check_against_non_runtime_checkable_protocol,
|
||||||
};
|
};
|
||||||
use crate::types::display::DisplaySettings;
|
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::list_members::all_members;
|
||||||
use crate::types::narrow::ClassInfoConstraintFunction;
|
use crate::types::narrow::ClassInfoConstraintFunction;
|
||||||
use crate::types::signatures::{CallableSignature, Signature};
|
use crate::types::signatures::{CallableSignature, Signature};
|
||||||
|
|
@ -82,8 +83,9 @@ use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, CallableTypeKind,
|
ApplyTypeMappingVisitor, BoundMethodType, BoundTypeVarInstance, CallableType, CallableTypeKind,
|
||||||
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
||||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
||||||
NormalizedVisitor, SpecialFormType, Truthiness, Type, TypeContext, TypeMapping, TypeRelation,
|
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
|
||||||
UnionBuilder, binding_type, definition_expression_type, walk_signature,
|
TypeContext, TypeMapping, TypeRelation, UnionBuilder, binding_type, definition_expression_type,
|
||||||
|
infer_definition_types, walk_signature,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
|
@ -499,13 +501,91 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
index,
|
index,
|
||||||
);
|
);
|
||||||
|
|
||||||
Signature::from_function(
|
let mut raw_signature = Signature::from_function(
|
||||||
db,
|
db,
|
||||||
pep695_ctx,
|
pep695_ctx,
|
||||||
definition,
|
definition,
|
||||||
function_stmt_node,
|
function_stmt_node,
|
||||||
has_implicitly_positional_first_parameter,
|
has_implicitly_positional_first_parameter,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
let generic_context = raw_signature.generic_context;
|
||||||
|
raw_signature.add_implicit_self_annotation(db, || {
|
||||||
|
if self.is_staticmethod(db) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have not yet added an implicit annotation to the `self` parameter, so any
|
||||||
|
// typevars that currently appear in the method's generic context come from explicit
|
||||||
|
// annotations.
|
||||||
|
let method_has_explicit_self = 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 Type::ClassLiteral(class_literal) = infer_definition_types(db, class_def)
|
||||||
|
.declaration_type(class_def)
|
||||||
|
.inner_type()
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let class_is_generic = class_literal.generic_context(db).is_some();
|
||||||
|
let class_is_fallback = class_literal
|
||||||
|
.known(db)
|
||||||
|
.is_some_and(KnownClass::is_fallback_class);
|
||||||
|
|
||||||
|
// Normally we implicitly annotate `self` or `cls` with `Self` or `type[Self]`, and
|
||||||
|
// create a `Self` typevar that we then have to solve for whenever this method is
|
||||||
|
// called. As an optimization, we can skip creating that typevar in certain situations:
|
||||||
|
//
|
||||||
|
// - The method cannot use explicit `Self` in any other parameter annotations,
|
||||||
|
// or in its return type. If it does, then we really do need specialization
|
||||||
|
// inference at each call site to see which specific instance type should be
|
||||||
|
// used in those other parameters / return type.
|
||||||
|
//
|
||||||
|
// - The class cannot be generic. If it is, then we might need an actual `Self`
|
||||||
|
// typevar to help carry through constraints that relate the instance type to
|
||||||
|
// other typevars in the method signature.
|
||||||
|
//
|
||||||
|
// - The class cannot be a "fallback class". A fallback class is used like a mixin,
|
||||||
|
// and so we need specialization inference to determine the "real" class that the
|
||||||
|
// fallback is augmenting. (See KnownClass::is_fallback_class for more details.)
|
||||||
|
if method_has_explicit_self || class_is_generic || class_is_fallback {
|
||||||
|
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();
|
||||||
|
|
||||||
|
let typing_self = typing_self(db, scope_id, typevar_binding_context, class).expect(
|
||||||
|
"We should always find the surrounding class \
|
||||||
|
for an implicit self: Self annotation",
|
||||||
|
);
|
||||||
|
|
||||||
|
if self.is_classmethod(db) {
|
||||||
|
Some(SubclassOfType::from(
|
||||||
|
db,
|
||||||
|
SubclassOfInner::TypeVar(typing_self),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some(Type::TypeVar(typing_self))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If skip creating the typevar, we use "instance of class" or "subclass of
|
||||||
|
// class" as the implicit annotation instead.
|
||||||
|
if self.is_classmethod(db) {
|
||||||
|
Some(SubclassOfType::from(
|
||||||
|
db,
|
||||||
|
SubclassOfInner::Class(ClassType::NonGeneric(class_literal)),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some(class_literal.to_non_generic_instance(db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
raw_signature
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parameter_span(
|
pub(crate) fn parameter_span(
|
||||||
|
|
|
||||||
|
|
@ -2241,7 +2241,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
name,
|
name,
|
||||||
type_params,
|
type_params,
|
||||||
parameters,
|
parameters,
|
||||||
returns,
|
returns: _,
|
||||||
body: _,
|
body: _,
|
||||||
decorator_list,
|
decorator_list,
|
||||||
} = function;
|
} = function;
|
||||||
|
|
@ -2288,21 +2288,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
self.infer_expression(default, TypeContext::default());
|
self.infer_expression(default, TypeContext::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are type params, parameters and returns are evaluated in that scope, that is, in
|
// If there are type params, parameters and returns are evaluated in that scope. Otherwise,
|
||||||
// `infer_function_type_params`, rather than here.
|
// we always defer the inference of the parameters and returns. That ensures that we do not
|
||||||
|
// add any spurious salsa cycles when applying decorators below. (Applying a decorator
|
||||||
|
// requires getting the signature of this function definition, which in turn requires
|
||||||
|
// (lazily) inferring the parameter and return types.)
|
||||||
if type_params.is_none() {
|
if type_params.is_none() {
|
||||||
if self.defer_annotations() {
|
self.deferred.insert(definition, self.multi_inference_state);
|
||||||
self.deferred.insert(definition, self.multi_inference_state);
|
|
||||||
} else {
|
|
||||||
let previous_typevar_binding_context =
|
|
||||||
self.typevar_binding_context.replace(definition);
|
|
||||||
self.infer_return_type_annotation(
|
|
||||||
returns.as_deref(),
|
|
||||||
DeferredExpressionState::None,
|
|
||||||
);
|
|
||||||
self.infer_parameters(parameters);
|
|
||||||
self.typevar_binding_context = previous_typevar_binding_context;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let known_function =
|
let known_function =
|
||||||
|
|
@ -2946,10 +2938,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
function: &ast::StmtFunctionDef,
|
function: &ast::StmtFunctionDef,
|
||||||
) {
|
) {
|
||||||
|
let mut prev_in_no_type_check = self.context.set_in_no_type_check(InNoTypeCheck::Yes);
|
||||||
|
for decorator in &function.decorator_list {
|
||||||
|
let decorator_type = self.infer_decorator(decorator);
|
||||||
|
if let Type::FunctionLiteral(function) = decorator_type
|
||||||
|
&& let Some(KnownFunction::NoTypeCheck) = function.known(self.db())
|
||||||
|
{
|
||||||
|
// If the function is decorated with the `no_type_check` decorator,
|
||||||
|
// we need to suppress any errors that come after the decorators.
|
||||||
|
prev_in_no_type_check = InNoTypeCheck::Yes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.context.set_in_no_type_check(prev_in_no_type_check);
|
||||||
|
|
||||||
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
|
let previous_typevar_binding_context = self.typevar_binding_context.replace(definition);
|
||||||
self.infer_return_type_annotation(
|
self.infer_return_type_annotation(
|
||||||
function.returns.as_deref(),
|
function.returns.as_deref(),
|
||||||
DeferredExpressionState::Deferred,
|
self.defer_annotations().into(),
|
||||||
);
|
);
|
||||||
self.infer_parameters(function.parameters.as_ref());
|
self.infer_parameters(function.parameters.as_ref());
|
||||||
self.typevar_binding_context = previous_typevar_binding_context;
|
self.typevar_binding_context = previous_typevar_binding_context;
|
||||||
|
|
|
||||||
|
|
@ -13,107 +13,46 @@
|
||||||
use std::{collections::HashMap, slice::Iter};
|
use std::{collections::HashMap, slice::Iter};
|
||||||
|
|
||||||
use itertools::{EitherOrBoth, Itertools};
|
use itertools::{EitherOrBoth, Itertools};
|
||||||
use ruff_db::parsed::parsed_module;
|
|
||||||
use ruff_python_ast::ParameterWithDefault;
|
|
||||||
use smallvec::{SmallVec, smallvec_inline};
|
use smallvec::{SmallVec, smallvec_inline};
|
||||||
|
|
||||||
use super::{
|
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type, semantic_index};
|
||||||
ClassType, DynamicType, Type, TypeVarVariance, definition_expression_type,
|
use crate::semantic_index::definition::Definition;
|
||||||
infer_definition_types, semantic_index,
|
|
||||||
};
|
|
||||||
use crate::semantic_index::definition::{Definition, DefinitionKind};
|
|
||||||
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
use crate::types::constraints::{ConstraintSet, IteratorConstraintsExtension};
|
||||||
use crate::types::function::{is_implicit_classmethod, is_implicit_staticmethod};
|
use crate::types::generics::{GenericContext, InferableTypeVars, walk_generic_context};
|
||||||
use crate::types::generics::{
|
use crate::types::infer::{infer_deferred_types, infer_scope_types};
|
||||||
GenericContext, InferableTypeVars, typing_self, walk_generic_context,
|
|
||||||
};
|
|
||||||
use crate::types::infer::nearest_enclosing_class;
|
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind, ClassLiteral,
|
ApplyTypeMappingVisitor, BindingContext, BoundTypeVarInstance, CallableTypeKind,
|
||||||
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
FindLegacyTypeVarsVisitor, HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor,
|
||||||
KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, SubclassOfInner,
|
KnownClass, MaterializationKind, NormalizedVisitor, ParamSpecAttrKind, TypeContext,
|
||||||
SubclassOfType, TypeContext, TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
TypeMapping, TypeRelation, VarianceInferable, todo_type,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet};
|
use crate::{Db, FxOrderSet};
|
||||||
use ruff_python_ast::{self as ast, name::Name};
|
use ruff_python_ast::{self as ast, name::Name};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
/// Infer the type of a parameter or return annotation in a function signature.
|
||||||
#[expect(clippy::struct_excessive_bools)]
|
///
|
||||||
struct MethodInformation<'db> {
|
/// This is very similar to [`definition_expression_type`], but knows that `TypeInferenceBuilder`
|
||||||
is_staticmethod: bool,
|
/// will always infer the parameters and return of a function in its PEP-695 typevar scope, if
|
||||||
is_classmethod: bool,
|
/// there is one; otherwise they will be inferred in the function definition scope, but will always
|
||||||
method_may_be_generic: bool,
|
/// be deferred. (This prevents spurious salsa cycles when we need the signature of the function
|
||||||
class_literal: ClassLiteral<'db>,
|
/// while in the middle of inferring its definition scope — for instance, when applying
|
||||||
class_is_generic: bool,
|
/// decorators.)
|
||||||
}
|
fn function_signature_expression_type<'db>(
|
||||||
|
|
||||||
fn infer_method_information<'db>(
|
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
) -> Option<MethodInformation<'db>> {
|
expression: &ast::Expr,
|
||||||
let DefinitionKind::Function(function_definition) = definition.kind(db) else {
|
) -> Type<'db> {
|
||||||
return None;
|
let file = definition.file(db);
|
||||||
};
|
|
||||||
|
|
||||||
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 index = semantic_index(db, file);
|
||||||
|
let file_scope = index.expression_scope_id(expression);
|
||||||
let class_scope = index.scope(class_scope_id.file_scope_id(db));
|
let scope = file_scope.to_scope_id(db, file);
|
||||||
let class_node = class_scope.node().as_class()?;
|
if scope == definition.scope(db) {
|
||||||
|
// expression is in the function definition scope, but always deferred
|
||||||
let function_node = function_definition.node(&module);
|
infer_deferred_types(db, definition).expression_type(expression)
|
||||||
let function_name = &function_node.name;
|
} else {
|
||||||
|
// expression is in the PEP-695 type params sub-scope
|
||||||
let mut is_staticmethod = is_implicit_classmethod(function_name);
|
infer_scope_types(db, scope).expression_type(expression)
|
||||||
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
|
/// The signature of a single callable. If the callable is overloaded, there is a separate
|
||||||
|
|
@ -615,7 +554,7 @@ impl<'db> Signature<'db> {
|
||||||
let return_ty = function_node
|
let return_ty = function_node
|
||||||
.returns
|
.returns
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|returns| definition_expression_type(db, definition, returns.as_ref()));
|
.map(|returns| function_signature_expression_type(db, definition, returns.as_ref()));
|
||||||
let legacy_generic_context =
|
let legacy_generic_context =
|
||||||
GenericContext::from_function_params(db, definition, ¶meters, return_ty);
|
GenericContext::from_function_params(db, definition, ¶meters, return_ty);
|
||||||
let full_generic_context = GenericContext::merge_pep695_and_legacy(
|
let full_generic_context = GenericContext::merge_pep695_and_legacy(
|
||||||
|
|
@ -771,6 +710,57 @@ impl<'db> Signature<'db> {
|
||||||
&self.parameters
|
&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<Type<'db>>,
|
||||||
|
) {
|
||||||
|
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.)
|
||||||
|
let self_typevar = match self_type {
|
||||||
|
Type::TypeVar(self_typevar) => Some(self_typevar),
|
||||||
|
Type::SubclassOf(subclass_of) => subclass_of.into_type_var(),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(self_typevar) = self_typevar {
|
||||||
|
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.
|
/// Return the definition associated with this signature, if any.
|
||||||
pub(crate) fn definition(&self) -> Option<Definition<'db>> {
|
pub(crate) fn definition(&self) -> Option<Definition<'db>> {
|
||||||
self.definition
|
self.definition
|
||||||
|
|
@ -1674,84 +1664,16 @@ impl<'db> Parameters<'db> {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let method_info = infer_method_information(db, definition);
|
|
||||||
let is_staticmethod = method_info.is_some_and(|f| f.is_staticmethod);
|
|
||||||
let is_classmethod = method_info.is_some_and(|f| f.is_classmethod);
|
|
||||||
|
|
||||||
let inferred_annotation = |arg: &ParameterWithDefault| {
|
|
||||||
if let Some(MethodInformation {
|
|
||||||
method_may_be_generic,
|
|
||||||
class_literal,
|
|
||||||
class_is_generic,
|
|
||||||
..
|
|
||||||
}) = method_info
|
|
||||||
&& !is_staticmethod
|
|
||||||
&& 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();
|
|
||||||
|
|
||||||
let typing_self = typing_self(db, scope_id, typevar_binding_context, class)
|
|
||||||
.expect("We should always find the surrounding class for an implicit self: Self annotation");
|
|
||||||
|
|
||||||
if is_classmethod {
|
|
||||||
Some(SubclassOfType::from(
|
|
||||||
db,
|
|
||||||
SubclassOfInner::TypeVar(typing_self),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Some(Type::TypeVar(typing_self))
|
|
||||||
}
|
|
||||||
} 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`, or the implicit
|
|
||||||
// `type[Self]` type of the `cls` parameter, would be the only type variable, so we can just
|
|
||||||
// use the class directly.
|
|
||||||
if is_classmethod {
|
|
||||||
Some(SubclassOfType::from(
|
|
||||||
db,
|
|
||||||
SubclassOfInner::Class(ClassType::NonGeneric(class_literal)),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
Some(class_literal.to_non_generic_instance(db))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let pos_only_param = |param: &ast::ParameterWithDefault| {
|
let pos_only_param = |param: &ast::ParameterWithDefault| {
|
||||||
if let Some(inferred_annotation_type) = inferred_annotation(param) {
|
Parameter::from_node_and_kind(
|
||||||
Parameter {
|
db,
|
||||||
annotated_type: Some(inferred_annotation_type),
|
definition,
|
||||||
inferred_annotation: true,
|
¶m.parameter,
|
||||||
kind: ParameterKind::PositionalOnly {
|
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,
|
|
||||||
¶m.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();
|
||||||
|
|
@ -1775,27 +1697,15 @@ impl<'db> Parameters<'db> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
|
let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
|
||||||
if let Some(inferred_annotation_type) = inferred_annotation(arg) {
|
Parameter::from_node_and_kind(
|
||||||
Parameter {
|
db,
|
||||||
annotated_type: Some(inferred_annotation_type),
|
definition,
|
||||||
inferred_annotation: true,
|
&arg.parameter,
|
||||||
kind: ParameterKind::PositionalOrKeyword {
|
ParameterKind::PositionalOrKeyword {
|
||||||
name: arg.parameter.name.id.clone(),
|
name: arg.parameter.name.id.clone(),
|
||||||
default_type: default_type(arg),
|
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| {
|
let variadic = vararg.as_ref().map(|arg| {
|
||||||
|
|
@ -2212,7 +2122,7 @@ impl<'db> Parameter<'db> {
|
||||||
Self {
|
Self {
|
||||||
annotated_type: parameter
|
annotated_type: parameter
|
||||||
.annotation()
|
.annotation()
|
||||||
.map(|annotation| definition_expression_type(db, definition, annotation)),
|
.map(|annotation| function_signature_expression_type(db, definition, annotation)),
|
||||||
kind,
|
kind,
|
||||||
form: ParameterForm::Value,
|
form: ParameterForm::Value,
|
||||||
inferred_annotation: false,
|
inferred_annotation: false,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue