[ty] Implement the legacy PEP-484 convention for indicating positional-only parameters (#20248)
Some checks are pending
CI / mkdocs (push) Waiting to run
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Alex Waygood 2025-09-05 17:56:06 +01:00 committed by GitHub
parent eb6154f792
commit 5d52902e18
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 376 additions and 150 deletions

View file

@ -49,6 +49,12 @@ pub struct AstNodeRef<T> {
_node: PhantomData<T>,
}
impl<T> AstNodeRef<T> {
pub(crate) fn index(&self) -> NodeIndex {
self.index
}
}
impl<T> AstNodeRef<T>
where
T: HasNodeIndex + Ranged + PartialEq + Debug,

View file

@ -1,5 +1,7 @@
use ruff_python_ast::{HasNodeIndex, NodeIndex};
use crate::ast_node_ref::AstNodeRef;
/// Compact key for a node for use in a hash map.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
pub(super) struct NodeKey(NodeIndex);
@ -11,4 +13,8 @@ impl NodeKey {
{
NodeKey(node.node_index().load())
}
pub(super) fn from_node_ref<T>(node_ref: &AstNodeRef<T>) -> Self {
NodeKey(node_ref.index())
}
}

View file

@ -159,7 +159,6 @@ pub(crate) fn attribute_scopes<'db, 's>(
class_body_scope: ScopeId<'db>,
) -> impl Iterator<Item = FileScopeId> + use<'s, 'db> {
let file = class_body_scope.file(db);
let module = parsed_module(db, file).load(db);
let index = semantic_index(db, file);
let class_scope_id = class_body_scope.file_scope_id(db);
@ -175,7 +174,7 @@ pub(crate) fn attribute_scopes<'db, 's>(
(child_scope_id, scope)
};
function_scope.node().as_function(&module)?;
function_scope.node().as_function()?;
Some(function_scope_id)
})
}
@ -332,6 +331,39 @@ impl<'db> SemanticIndex<'db> {
Some(&self.scopes[self.parent_scope_id(scope_id)?])
}
/// Return the [`Definition`] of the class enclosing this method, given the
/// method's body scope, or `None` if it is not a method.
pub(crate) fn class_definition_of_method(
&self,
function_body_scope: FileScopeId,
) -> Option<Definition<'db>> {
let current_scope = self.scope(function_body_scope);
if current_scope.kind() != ScopeKind::Function {
return None;
}
let parent_scope_id = current_scope.parent()?;
let parent_scope = self.scope(parent_scope_id);
let class_scope = match parent_scope.kind() {
ScopeKind::Class => parent_scope,
ScopeKind::TypeParams => {
let class_scope_id = parent_scope.parent()?;
let potentially_class_scope = self.scope(class_scope_id);
match potentially_class_scope.kind() {
ScopeKind::Class => potentially_class_scope,
_ => return None,
}
}
_ => return None,
};
class_scope
.node()
.as_class()
.map(|node_ref| self.expect_single_definition(node_ref))
}
fn is_scope_reachable(&self, db: &'db dyn Db, scope_id: FileScopeId) -> bool {
self.parent_scope_id(scope_id)
.is_none_or(|parent_scope_id| {

View file

@ -2644,7 +2644,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
match scope.kind() {
ScopeKind::Class | ScopeKind::Lambda => return false,
ScopeKind::Function => {
return scope.node().expect_function(self.module).is_async;
return scope.node().expect_function().node(self.module).is_async;
}
ScopeKind::Comprehension
| ScopeKind::Module

View file

@ -1227,3 +1227,12 @@ impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey {
Self(NodeKey::from_node(value))
}
}
impl<T> From<&AstNodeRef<T>> for DefinitionNodeKey
where
for<'a> &'a T: Into<DefinitionNodeKey>,
{
fn from(value: &AstNodeRef<T>) -> Self {
Self(NodeKey::from_node_ref(value))
}
}

View file

@ -397,52 +397,38 @@ impl NodeWithScopeKind {
}
}
pub(crate) fn expect_class<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtClassDef {
pub(crate) fn as_class(&self) -> Option<&AstNodeRef<ast::StmtClassDef>> {
match self {
Self::Class(class) => class.node(module),
_ => panic!("expected class"),
}
}
pub(crate) fn as_class<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> Option<&'ast ast::StmtClassDef> {
match self {
Self::Class(class) => Some(class.node(module)),
Self::Class(class) => Some(class),
_ => None,
}
}
pub(crate) fn expect_function<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtFunctionDef {
self.as_function(module).expect("expected function")
pub(crate) fn expect_class(&self) -> &AstNodeRef<ast::StmtClassDef> {
self.as_class().expect("expected class")
}
pub(crate) fn expect_type_alias<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> &'ast ast::StmtTypeAlias {
pub(crate) fn as_function(&self) -> Option<&AstNodeRef<ast::StmtFunctionDef>> {
match self {
Self::TypeAlias(type_alias) => type_alias.node(module),
_ => panic!("expected type alias"),
}
}
pub(crate) fn as_function<'ast>(
&self,
module: &'ast ParsedModuleRef,
) -> Option<&'ast ast::StmtFunctionDef> {
match self {
Self::Function(function) => Some(function.node(module)),
Self::Function(function) => Some(function),
_ => None,
}
}
pub(crate) fn expect_function(&self) -> &AstNodeRef<ast::StmtFunctionDef> {
self.as_function().expect("expected function")
}
pub(crate) fn as_type_alias(&self) -> Option<&AstNodeRef<ast::StmtTypeAlias>> {
match self {
Self::TypeAlias(type_alias) => Some(type_alias),
_ => None,
}
}
pub(crate) fn expect_type_alias(&self) -> &AstNodeRef<ast::StmtTypeAlias> {
self.as_type_alias().expect("expected type alias")
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]

View file

@ -5649,7 +5649,7 @@ impl<'db> Type<'db> {
SpecialFormType::TypingSelf => {
let module = parsed_module(db, scope_id.file(db)).load(db);
let index = semantic_index(db, scope_id.file(db));
let Some(class) = nearest_enclosing_class(db, index, scope_id, &module) else {
let Some(class) = nearest_enclosing_class(db, index, scope_id) else {
return Err(InvalidTypeExpressionError {
fallback_type: Type::unknown(),
invalid_expressions: smallvec::smallvec_inline![
@ -9364,9 +9364,7 @@ fn walk_pep_695_type_alias<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
impl<'db> PEP695TypeAliasType<'db> {
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let scope = self.rhs_scope(db);
let module = parsed_module(db, scope.file(db)).load(db);
let type_alias_stmt_node = scope.node(db).expect_type_alias(&module);
let type_alias_stmt_node = scope.node(db).expect_type_alias();
semantic_index(db, scope.file(db)).expect_single_definition(type_alias_stmt_node)
}
@ -9374,9 +9372,9 @@ impl<'db> PEP695TypeAliasType<'db> {
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
let scope = self.rhs_scope(db);
let module = parsed_module(db, scope.file(db)).load(db);
let type_alias_stmt_node = scope.node(db).expect_type_alias(&module);
let type_alias_stmt_node = scope.node(db).expect_type_alias();
let definition = self.definition(db);
definition_expression_type(db, definition, &type_alias_stmt_node.value)
definition_expression_type(db, definition, &type_alias_stmt_node.node(&module).value)
}
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {

View file

@ -1403,7 +1403,7 @@ impl<'db> ClassLiteral<'db> {
let scope = self.body_scope(db);
let file = scope.file(db);
let parsed = parsed_module(db, file).load(db);
let class_def_node = scope.node(db).expect_class(&parsed);
let class_def_node = scope.node(db).expect_class().node(&parsed);
class_def_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
let definition = index.expect_single_definition(class_def_node);
@ -1445,14 +1445,13 @@ impl<'db> ClassLiteral<'db> {
/// query depends on the AST of another file (bad!).
fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef {
let scope = self.body_scope(db);
scope.node(db).expect_class(module)
scope.node(db).expect_class().node(module)
}
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let body_scope = self.body_scope(db);
let module = parsed_module(db, body_scope.file(db)).load(db);
let index = semantic_index(db, body_scope.file(db));
index.expect_single_definition(body_scope.node(db).expect_class(&module))
index.expect_single_definition(body_scope.node(db).expect_class())
}
pub(crate) fn apply_specialization(
@ -2870,8 +2869,8 @@ impl<'db> ClassLiteral<'db> {
let class_table = place_table(db, class_body_scope);
let is_valid_scope = |method_scope: ScopeId<'db>| {
if let Some(method_def) = method_scope.node(db).as_function(&module) {
let method_name = method_def.name.as_str();
if let Some(method_def) = method_scope.node(db).as_function() {
let method_name = method_def.node(&module).name.as_str();
if let Place::Type(Type::FunctionLiteral(method_type), _) =
class_symbol(db, class_body_scope, method_name).place
{
@ -2946,20 +2945,22 @@ impl<'db> ClassLiteral<'db> {
}
// The attribute assignment inherits the reachability of the method which contains it
let is_method_reachable =
if let Some(method_def) = method_scope.node(db).as_function(&module) {
let method = index.expect_single_definition(method_def);
let method_place = class_table.symbol_id(&method_def.name).unwrap();
class_map
.all_reachable_symbol_bindings(method_place)
.find_map(|bind| {
(bind.binding.is_defined_and(|def| def == method))
.then(|| class_map.binding_reachability(db, &bind))
})
.unwrap_or(Truthiness::AlwaysFalse)
} else {
Truthiness::AlwaysFalse
};
let is_method_reachable = if let Some(method_def) = method_scope.node(db).as_function()
{
let method = index.expect_single_definition(method_def);
let method_place = class_table
.symbol_id(&method_def.node(&module).name)
.unwrap();
class_map
.all_reachable_symbol_bindings(method_place)
.find_map(|bind| {
(bind.binding.is_defined_and(|def| def == method))
.then(|| class_map.binding_reachability(db, &bind))
})
.unwrap_or(Truthiness::AlwaysFalse)
} else {
Truthiness::AlwaysFalse
};
if is_method_reachable.is_always_false() {
continue;
}
@ -3323,7 +3324,7 @@ impl<'db> ClassLiteral<'db> {
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
let class_scope = self.body_scope(db);
let module = parsed_module(db, class_scope.file(db)).load(db);
let class_node = class_scope.node(db).expect_class(&module);
let class_node = class_scope.node(db).expect_class().node(&module);
let class_name = &class_node.name;
TextRange::new(
class_name.start(),
@ -4784,8 +4785,7 @@ impl KnownClass {
// 2. The first parameter of the current function (typically `self` or `cls`)
match overload.parameter_types() {
[] => {
let Some(enclosing_class) =
nearest_enclosing_class(db, index, scope, module)
let Some(enclosing_class) = nearest_enclosing_class(db, index, scope)
else {
BoundSuperError::UnavailableImplicitArguments
.report_diagnostic(context, call_expression.into());

View file

@ -172,7 +172,7 @@ impl<'db, 'ast> InferContext<'db, 'ast> {
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
let mut function_scope_tys = index
.ancestor_scopes(scope_id)
.filter_map(|(_, scope)| scope.node().as_function(self.module()))
.filter_map(|(_, scope)| scope.node().as_function())
.map(|node| binding_type(self.db, index.expect_single_definition(node)))
.filter_map(Type::into_function_literal);

View file

@ -200,15 +200,15 @@ impl ClassDisplay<'_> {
match ancestor_scope.kind() {
ScopeKind::Class => {
if let Some(class_def) = node.as_class(&module_ast) {
name_parts.push(class_def.name.as_str().to_string());
if let Some(class_def) = node.as_class() {
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
}
}
ScopeKind::Function => {
if let Some(function_def) = node.as_function(&module_ast) {
if let Some(function_def) = node.as_function() {
name_parts.push(format!(
"<locals of function '{}'>",
function_def.name.as_str()
function_def.node(&module_ast).name.as_str()
));
}
}

View file

@ -55,7 +55,7 @@ use bitflags::bitflags;
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
use ruff_db::files::{File, FileRange};
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
use ruff_python_ast as ast;
use ruff_python_ast::{self as ast, ParameterWithDefault};
use ruff_text_size::Ranged;
use crate::module_resolver::{KnownModule, file_to_module};
@ -63,7 +63,7 @@ use crate::place::{Boundness, Place, place_from_bindings};
use crate::semantic_index::ast_ids::HasScopedUseId;
use crate::semantic_index::definition::Definition;
use crate::semantic_index::scope::ScopeId;
use crate::semantic_index::semantic_index;
use crate::semantic_index::{FileScopeId, SemanticIndex, semantic_index};
use crate::types::call::{Binding, CallArguments};
use crate::types::constraints::{ConstraintSet, Constraints};
use crate::types::context::InferContext;
@ -80,7 +80,7 @@ use crate::types::{
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
IsEquivalentVisitor, KnownClass, NormalizedVisitor, SpecialFormType, Truthiness, Type,
TypeMapping, TypeRelation, UnionBuilder, all_members, walk_type_mapping,
TypeMapping, TypeRelation, UnionBuilder, all_members, binding_type, walk_type_mapping,
};
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
@ -236,6 +236,22 @@ impl<'db> OverloadLiteral<'db> {
self.has_known_decorator(db, FunctionDecorators::OVERLOAD)
}
/// Returns true if this overload is decorated with `@staticmethod`, or if it is implicitly a
/// staticmethod.
pub(crate) fn is_staticmethod(self, db: &dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::STATICMETHOD) || self.name(db) == "__new__"
}
/// Returns true if this overload is decorated with `@classmethod`, or if it is implicitly a
/// classmethod.
pub(crate) fn is_classmethod(self, db: &dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|| matches!(
self.name(db).as_str(),
"__init_subclass__" | "__class_getitem__"
)
}
fn node<'ast>(
self,
db: &dyn Db,
@ -249,7 +265,7 @@ impl<'db> OverloadLiteral<'db> {
the function is defined."
);
self.body_scope(db).node(db).expect_function(module)
self.body_scope(db).node(db).expect_function().node(module)
}
/// Returns the [`FileRange`] of the function's name.
@ -258,7 +274,8 @@ impl<'db> OverloadLiteral<'db> {
self.file(db),
self.body_scope(db)
.node(db)
.expect_function(module)
.expect_function()
.node(module)
.name
.range,
)
@ -274,9 +291,8 @@ impl<'db> OverloadLiteral<'db> {
/// over-invalidation.
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
let body_scope = self.body_scope(db);
let module = parsed_module(db, self.file(db)).load(db);
let index = semantic_index(db, body_scope.file(db));
index.expect_single_definition(body_scope.node(db).expect_function(&module))
index.expect_single_definition(body_scope.node(db).expect_function())
}
/// Returns the overload immediately before this one in the AST. Returns `None` if there is no
@ -290,7 +306,8 @@ impl<'db> OverloadLiteral<'db> {
let use_id = self
.body_scope(db)
.node(db)
.expect_function(&module)
.expect_function()
.node(&module)
.name
.scoped_use_id(db, scope);
@ -325,17 +342,79 @@ impl<'db> OverloadLiteral<'db> {
db: &'db dyn Db,
inherited_generic_context: Option<GenericContext<'db>>,
) -> Signature<'db> {
/// `self` or `cls` can be implicitly positional-only if:
/// - It is a method AND
/// - No parameters in the method use PEP-570 syntax AND
/// - It is not a `@staticmethod` AND
/// - `self`/`cls` is not explicitly positional-only using the PEP-484 convention AND
/// - Either the next parameter after `self`/`cls` uses the PEP-484 convention,
/// or the enclosing class is a `Protocol` class
fn has_implicitly_positional_only_first_param<'db>(
db: &'db dyn Db,
literal: OverloadLiteral<'db>,
node: &ast::StmtFunctionDef,
scope: FileScopeId,
index: &SemanticIndex,
) -> bool {
let parameters = &node.parameters;
if !parameters.posonlyargs.is_empty() {
return false;
}
let Some(first_param) = parameters.args.first() else {
return false;
};
if first_param.uses_pep_484_positional_only_convention() {
return false;
}
if literal.is_staticmethod(db) {
return false;
}
let Some(class_definition) = index.class_definition_of_method(scope) else {
return false;
};
// `self` and `cls` are always positional-only if the next parameter uses the
// PEP-484 convention.
if parameters
.args
.get(1)
.is_some_and(ParameterWithDefault::uses_pep_484_positional_only_convention)
{
return true;
}
// If there isn't any parameter other than `self`/`cls`,
// or there is but it isn't using the PEP-484 convention,
// then `self`/`cls` are only implicitly positional-only if
// it is a protocol class.
let class_type = binding_type(db, class_definition);
class_type
.to_class_type(db)
.is_some_and(|class| class.is_protocol(db))
}
let scope = self.body_scope(db);
let module = parsed_module(db, self.file(db)).load(db);
let function_stmt_node = scope.node(db).expect_function(&module);
let function_stmt_node = scope.node(db).expect_function().node(&module);
let definition = self.definition(db);
let index = semantic_index(db, scope.file(db));
let generic_context = function_stmt_node.type_params.as_ref().map(|type_params| {
let index = semantic_index(db, scope.file(db));
GenericContext::from_type_params(db, index, definition, type_params)
});
let index = semantic_index(db, scope.file(db));
let is_generator = scope.file_scope_id(db).is_generator_function(index);
let file_scope_id = scope.file_scope_id(db);
let is_generator = file_scope_id.is_generator_function(index);
let has_implicitly_positional_first_parameter = has_implicitly_positional_only_first_param(
db,
self,
function_stmt_node,
file_scope_id,
index,
);
Signature::from_function(
db,
@ -344,6 +423,7 @@ impl<'db> OverloadLiteral<'db> {
definition,
function_stmt_node,
is_generator,
has_implicitly_positional_first_parameter,
)
}
@ -356,7 +436,7 @@ impl<'db> OverloadLiteral<'db> {
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let module = parsed_module(db, self.file(db)).load(db);
let func_def = node.as_function(&module)?;
let func_def = node.as_function()?.node(&module);
let range = parameter_index
.and_then(|parameter_index| {
func_def
@ -376,7 +456,7 @@ impl<'db> OverloadLiteral<'db> {
let span = Span::from(function_scope.file(db));
let node = function_scope.node(db);
let module = parsed_module(db, self.file(db)).load(db);
let func_def = node.as_function(&module)?;
let func_def = node.as_function()?.node(&module);
let return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
let mut signature = func_def.name.range.cover(func_def.parameters.range);
if let Some(return_type_range) = return_type_range {
@ -713,17 +793,15 @@ impl<'db> FunctionType<'db> {
/// Returns true if this method is decorated with `@classmethod`, or if it is implicitly a
/// classmethod.
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|| matches!(
self.name(db).as_str(),
"__init_subclass__" | "__class_getitem__"
)
self.iter_overloads_and_implementation(db)
.any(|overload| overload.is_classmethod(db))
}
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
/// static method.
pub(crate) fn is_staticmethod(self, db: &'db dyn Db) -> bool {
self.has_known_decorator(db, FunctionDecorators::STATICMETHOD) || self.name(db) == "__new__"
self.iter_overloads_and_implementation(db)
.any(|overload| overload.is_staticmethod(db))
}
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.

View file

@ -422,12 +422,11 @@ pub(crate) fn nearest_enclosing_class<'db>(
db: &'db dyn Db,
semantic: &SemanticIndex<'db>,
scope: ScopeId,
parsed: &ParsedModuleRef,
) -> Option<ClassLiteral<'db>> {
semantic
.ancestor_scopes(scope.file_scope_id(db))
.find_map(|(_, ancestor_scope)| {
let class = ancestor_scope.node().as_class(parsed)?;
let class = ancestor_scope.node().as_class()?;
let definition = semantic.expect_single_definition(class);
infer_definition_types(db, definition)
.declaration_type(definition)
@ -2418,29 +2417,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
/// behaviour to the [`nearest_enclosing_class`] function.
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
let current_scope_id = self.scope().file_scope_id(self.db());
let current_scope = self.index.scope(current_scope_id);
if current_scope.kind() != ScopeKind::Function {
return None;
}
let parent_scope_id = current_scope.parent()?;
let parent_scope = self.index.scope(parent_scope_id);
let class_scope = match parent_scope.kind() {
ScopeKind::Class => parent_scope,
ScopeKind::TypeParams => {
let class_scope_id = parent_scope.parent()?;
let potentially_class_scope = self.index.scope(class_scope_id);
match potentially_class_scope.kind() {
ScopeKind::Class => potentially_class_scope,
_ => return None,
}
}
_ => return None,
};
let class_stmt = class_scope.node().as_class(self.module())?;
let class_definition = self.index.expect_single_definition(class_stmt);
let class_definition = self.index.class_definition_of_method(current_scope_id)?;
binding_type(self.db(), class_definition).to_class_type(self.db())
}
@ -2453,7 +2430,10 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
if !current_scope.kind().is_non_lambda_function() {
return None;
}
current_scope.node().as_function(self.module())
current_scope
.node()
.as_function()
.map(|node_ref| node_ref.node(self.module()))
}
fn function_decorator_types<'a>(

View file

@ -12,7 +12,7 @@
use std::{collections::HashMap, slice::Iter};
use itertools::EitherOrBoth;
use itertools::{EitherOrBoth, Itertools};
use smallvec::{SmallVec, smallvec_inline};
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
@ -352,9 +352,14 @@ impl<'db> Signature<'db> {
definition: Definition<'db>,
function_node: &ast::StmtFunctionDef,
is_generator: bool,
has_implicitly_positional_first_parameter: bool,
) -> Self {
let parameters =
Parameters::from_parameters(db, definition, function_node.parameters.as_ref());
let parameters = Parameters::from_parameters(
db,
definition,
function_node.parameters.as_ref(),
has_implicitly_positional_first_parameter,
);
let return_ty = function_node.returns.as_ref().map(|returns| {
let plain_return_ty = definition_expression_type(db, definition, returns.as_ref())
.apply_type_mapping(
@ -1139,6 +1144,7 @@ impl<'db> Parameters<'db> {
db: &'db dyn Db,
definition: Definition<'db>,
parameters: &ast::Parameters,
has_implicitly_positional_first_parameter: bool,
) -> Self {
let ast::Parameters {
posonlyargs,
@ -1149,23 +1155,46 @@ impl<'db> Parameters<'db> {
range: _,
node_index: _,
} = parameters;
let default_type = |param: &ast::ParameterWithDefault| {
param
.default()
.map(|default| definition_expression_type(db, definition, default))
};
let positional_only = posonlyargs.iter().map(|arg| {
let pos_only_param = |param: &ast::ParameterWithDefault| {
Parameter::from_node_and_kind(
db,
definition,
&arg.parameter,
&param.parameter,
ParameterKind::PositionalOnly {
name: Some(arg.parameter.name.id.clone()),
default_type: default_type(arg),
name: Some(param.parameter.name.id.clone()),
default_type: default_type(param),
},
)
});
let positional_or_keyword = args.iter().map(|arg| {
};
let mut positional_only: Vec<Parameter> = 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),
);
}
let positional_or_keyword = pos_or_keyword_iter.map(|arg| {
Parameter::from_node_and_kind(
db,
definition,
@ -1176,6 +1205,7 @@ impl<'db> Parameters<'db> {
},
)
});
let variadic = vararg.as_ref().map(|arg| {
Parameter::from_node_and_kind(
db,
@ -1186,6 +1216,7 @@ impl<'db> Parameters<'db> {
},
)
});
let keyword_only = kwonlyargs.iter().map(|arg| {
Parameter::from_node_and_kind(
db,
@ -1197,6 +1228,7 @@ impl<'db> Parameters<'db> {
},
)
});
let keywords = kwarg.as_ref().map(|arg| {
Parameter::from_node_and_kind(
db,
@ -1207,8 +1239,10 @@ impl<'db> Parameters<'db> {
},
)
});
Self::new(
positional_only
.into_iter()
.chain(positional_or_keyword)
.chain(variadic)
.chain(keyword_only)