mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:34:57 +00:00
[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
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:
parent
eb6154f792
commit
5d52902e18
17 changed files with 376 additions and 150 deletions
|
@ -1,6 +1,6 @@
|
||||||
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
use ruff_macros::{ViolationMetadata, derive_message_formats};
|
||||||
|
use ruff_python_ast as ast;
|
||||||
use ruff_python_ast::identifier::Identifier;
|
use ruff_python_ast::identifier::Identifier;
|
||||||
use ruff_python_ast::{self as ast, ParameterWithDefault};
|
|
||||||
use ruff_python_semantic::analyze::function_type;
|
use ruff_python_semantic::analyze::function_type;
|
||||||
|
|
||||||
use crate::Violation;
|
use crate::Violation;
|
||||||
|
@ -85,16 +85,9 @@ pub(crate) fn pep_484_positional_parameter(checker: &Checker, function_def: &ast
|
||||||
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod
|
function_type::FunctionType::Method | function_type::FunctionType::ClassMethod
|
||||||
));
|
));
|
||||||
|
|
||||||
if let Some(arg) = function_def.parameters.args.get(skip) {
|
if let Some(param) = function_def.parameters.args.get(skip) {
|
||||||
if is_old_style_positional_only(arg) {
|
if param.uses_pep_484_positional_only_convention() {
|
||||||
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, arg.identifier());
|
checker.report_diagnostic(Pep484StylePositionalOnlyParameter, param.identifier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the [`ParameterWithDefault`] is an old-style positional-only parameter (i.e.,
|
|
||||||
/// its name starts with `__` and does not end with `__`).
|
|
||||||
fn is_old_style_positional_only(param: &ParameterWithDefault) -> bool {
|
|
||||||
let arg_name = param.name();
|
|
||||||
arg_name.starts_with("__") && !arg_name.ends_with("__")
|
|
||||||
}
|
|
||||||
|
|
|
@ -3219,7 +3219,6 @@ impl<'a> IntoIterator for &'a Box<Parameters> {
|
||||||
/// Used by `Arguments` original type.
|
/// Used by `Arguments` original type.
|
||||||
///
|
///
|
||||||
/// NOTE: This type is different from original Python AST.
|
/// NOTE: This type is different from original Python AST.
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
#[cfg_attr(feature = "get-size", derive(get_size2::GetSize))]
|
||||||
pub struct ParameterWithDefault {
|
pub struct ParameterWithDefault {
|
||||||
|
@ -3241,6 +3240,14 @@ impl ParameterWithDefault {
|
||||||
pub fn annotation(&self) -> Option<&Expr> {
|
pub fn annotation(&self) -> Option<&Expr> {
|
||||||
self.parameter.annotation()
|
self.parameter.annotation()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub fn uses_pep_484_positional_only_convention(&self) -> bool {
|
||||||
|
let name = self.name();
|
||||||
|
name.starts_with("__") && !name.ends_with("__")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An AST node used to represent the arguments passed to a function call or class definition.
|
/// An AST node used to represent the arguments passed to a function call or class definition.
|
||||||
|
|
|
@ -68,6 +68,78 @@ def _(flag: bool):
|
||||||
reveal_type(foo()) # revealed: int
|
reveal_type(foo()) # revealed: int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## PEP-484 convention for positional-only parameters
|
||||||
|
|
||||||
|
PEP 570, introduced in Python 3.8, added dedicated Python syntax for denoting positional-only
|
||||||
|
parameters (the `/` in a function signature). However, functions implemented in C were able to have
|
||||||
|
positional-only parameters prior to Python 3.8 (there was just no syntax for expressing this at the
|
||||||
|
Python level).
|
||||||
|
|
||||||
|
Stub files describing functions implemented in C nonetheless needed a way of expressing that certain
|
||||||
|
parameters were positional-only. In the absence of dedicated Python syntax, PEP 484 described a
|
||||||
|
convention that type checkers were expected to understand:
|
||||||
|
|
||||||
|
> Some functions are designed to take their arguments only positionally, and expect their callers
|
||||||
|
> never to use the argument’s name to provide that argument by keyword. All arguments with names
|
||||||
|
> beginning with `__` are assumed to be positional-only, except if their names also end with `__`.
|
||||||
|
|
||||||
|
While this convention is now redundant (following the implementation of PEP 570), many projects
|
||||||
|
still continue to use the old convention, so it is supported by ty as well.
|
||||||
|
|
||||||
|
```py
|
||||||
|
def f(__x: int): ...
|
||||||
|
|
||||||
|
f(1)
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [unknown-argument]
|
||||||
|
f(__x=1)
|
||||||
|
```
|
||||||
|
|
||||||
|
But not if they follow a non-positional-only parameter:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def g(x: int, __y: str): ...
|
||||||
|
|
||||||
|
g(x=1, __y="foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
And also not if they both start and end with `__`:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def h(__x__: str): ...
|
||||||
|
|
||||||
|
h(__x__="foo")
|
||||||
|
```
|
||||||
|
|
||||||
|
And if *any* parameters use the new PEP-570 convention, the old convention does not apply:
|
||||||
|
|
||||||
|
```py
|
||||||
|
def i(x: str, /, __y: int): ...
|
||||||
|
|
||||||
|
i("foo", __y=42) # fine
|
||||||
|
```
|
||||||
|
|
||||||
|
And `self`/`cls` are implicitly positional-only:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class C:
|
||||||
|
def method(self, __x: int): ...
|
||||||
|
@classmethod
|
||||||
|
def class_method(cls, __x: str): ...
|
||||||
|
# (the name of the first parameter is irrelevant;
|
||||||
|
# a staticmethod works the same as a free function in the global scope)
|
||||||
|
@staticmethod
|
||||||
|
def static_method(self, __x: int): ...
|
||||||
|
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [unknown-argument]
|
||||||
|
C().method(__x=1)
|
||||||
|
# error: [missing-argument]
|
||||||
|
# error: [unknown-argument]
|
||||||
|
C.class_method(__x="1")
|
||||||
|
C.static_method("x", __x=42) # fine
|
||||||
|
```
|
||||||
|
|
||||||
## Splatted arguments
|
## Splatted arguments
|
||||||
|
|
||||||
### Unknown argument length
|
### Unknown argument length
|
||||||
|
@ -545,7 +617,7 @@ def _(args: str) -> None:
|
||||||
|
|
||||||
This is a regression that was highlighted by the ecosystem check, which shows that we might need to
|
This is a regression that was highlighted by the ecosystem check, which shows that we might need to
|
||||||
rethink how we perform argument expansion during overload resolution. In particular, we might need
|
rethink how we perform argument expansion during overload resolution. In particular, we might need
|
||||||
to retry both `match_parameters` _and_ `check_types` for each expansion. Currently we only retry
|
to retry both `match_parameters` *and* `check_types` for each expansion. Currently we only retry
|
||||||
`check_types`.
|
`check_types`.
|
||||||
|
|
||||||
The issue is that argument expansion might produce a splatted value with a different arity than what
|
The issue is that argument expansion might produce a splatted value with a different arity than what
|
||||||
|
|
|
@ -413,13 +413,13 @@ To see the kinds and types of the protocol members, you can use the debugging ai
|
||||||
from ty_extensions import reveal_protocol_interface
|
from ty_extensions import reveal_protocol_interface
|
||||||
from typing import SupportsIndex, SupportsAbs, ClassVar, Iterator
|
from typing import SupportsIndex, SupportsAbs, ClassVar, Iterator
|
||||||
|
|
||||||
# revealed: {"method_member": MethodMember(`(self) -> bytes`), "x": AttributeMember(`int`), "y": PropertyMember { getter: `def y(self) -> str` }, "z": PropertyMember { getter: `def z(self) -> int`, setter: `def z(self, z: int) -> None` }}
|
# revealed: {"method_member": MethodMember(`(self, /) -> bytes`), "x": AttributeMember(`int`), "y": PropertyMember { getter: `def y(self, /) -> str` }, "z": PropertyMember { getter: `def z(self, /) -> int`, setter: `def z(self, /, z: int) -> None` }}
|
||||||
reveal_protocol_interface(Foo)
|
reveal_protocol_interface(Foo)
|
||||||
# revealed: {"__index__": MethodMember(`(self) -> int`)}
|
# revealed: {"__index__": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(SupportsIndex)
|
reveal_protocol_interface(SupportsIndex)
|
||||||
# revealed: {"__abs__": MethodMember(`(self) -> Unknown`)}
|
# revealed: {"__abs__": MethodMember(`(self, /) -> Unknown`)}
|
||||||
reveal_protocol_interface(SupportsAbs)
|
reveal_protocol_interface(SupportsAbs)
|
||||||
# revealed: {"__iter__": MethodMember(`(self) -> Iterator[Unknown]`), "__next__": MethodMember(`(self) -> Unknown`)}
|
# revealed: {"__iter__": MethodMember(`(self, /) -> Iterator[Unknown]`), "__next__": MethodMember(`(self, /) -> Unknown`)}
|
||||||
reveal_protocol_interface(Iterator)
|
reveal_protocol_interface(Iterator)
|
||||||
|
|
||||||
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"
|
# error: [invalid-argument-type] "Invalid argument to `reveal_protocol_interface`: Only protocol classes can be passed to `reveal_protocol_interface`"
|
||||||
|
@ -439,9 +439,9 @@ do not implement any special handling for generic aliases passed to the function
|
||||||
reveal_type(get_protocol_members(SupportsAbs[int])) # revealed: frozenset[str]
|
reveal_type(get_protocol_members(SupportsAbs[int])) # revealed: frozenset[str]
|
||||||
reveal_type(get_protocol_members(Iterator[int])) # revealed: frozenset[str]
|
reveal_type(get_protocol_members(Iterator[int])) # revealed: frozenset[str]
|
||||||
|
|
||||||
# revealed: {"__abs__": MethodMember(`(self) -> int`)}
|
# revealed: {"__abs__": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(SupportsAbs[int])
|
reveal_protocol_interface(SupportsAbs[int])
|
||||||
# revealed: {"__iter__": MethodMember(`(self) -> Iterator[int]`), "__next__": MethodMember(`(self) -> int`)}
|
# revealed: {"__iter__": MethodMember(`(self, /) -> Iterator[int]`), "__next__": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(Iterator[int])
|
reveal_protocol_interface(Iterator[int])
|
||||||
|
|
||||||
class BaseProto(Protocol):
|
class BaseProto(Protocol):
|
||||||
|
@ -450,10 +450,10 @@ class BaseProto(Protocol):
|
||||||
class SubProto(BaseProto, Protocol):
|
class SubProto(BaseProto, Protocol):
|
||||||
def member(self) -> bool: ...
|
def member(self) -> bool: ...
|
||||||
|
|
||||||
# revealed: {"member": MethodMember(`(self) -> int`)}
|
# revealed: {"member": MethodMember(`(self, /) -> int`)}
|
||||||
reveal_protocol_interface(BaseProto)
|
reveal_protocol_interface(BaseProto)
|
||||||
|
|
||||||
# revealed: {"member": MethodMember(`(self) -> bool`)}
|
# revealed: {"member": MethodMember(`(self, /) -> bool`)}
|
||||||
reveal_protocol_interface(SubProto)
|
reveal_protocol_interface(SubProto)
|
||||||
|
|
||||||
class ProtoWithClassVar(Protocol):
|
class ProtoWithClassVar(Protocol):
|
||||||
|
@ -1767,7 +1767,7 @@ class Foo(Protocol):
|
||||||
def method(self) -> str: ...
|
def method(self) -> str: ...
|
||||||
|
|
||||||
def f(x: Foo):
|
def f(x: Foo):
|
||||||
reveal_type(type(x).method) # revealed: def method(self) -> str
|
reveal_type(type(x).method) # revealed: def method(self, /) -> str
|
||||||
|
|
||||||
class Bar:
|
class Bar:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -1776,6 +1776,31 @@ class Bar:
|
||||||
f(Bar()) # error: [invalid-argument-type]
|
f(Bar()) # error: [invalid-argument-type]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Some protocols use the old convention (specified in PEP-484) for denoting positional-only
|
||||||
|
parameters. This is supported by ty:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class HasPosOnlyDunders:
|
||||||
|
def __invert__(self, /) -> "HasPosOnlyDunders":
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __lt__(self, other, /) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
class SupportsLessThan(Protocol):
|
||||||
|
def __lt__(self, __other) -> bool: ...
|
||||||
|
|
||||||
|
class Invertable(Protocol):
|
||||||
|
# `self` and `cls` are always implicitly positional-only for methods defined in `Protocol`
|
||||||
|
# classes, even if no parameters in the method use the PEP-484 convention.
|
||||||
|
def __invert__(self) -> object: ...
|
||||||
|
|
||||||
|
static_assert(is_assignable_to(HasPosOnlyDunders, SupportsLessThan))
|
||||||
|
static_assert(is_assignable_to(HasPosOnlyDunders, Invertable))
|
||||||
|
static_assert(is_assignable_to(str, SupportsLessThan))
|
||||||
|
static_assert(is_assignable_to(int, Invertable))
|
||||||
|
```
|
||||||
|
|
||||||
## Equivalence of protocols with method or property members
|
## Equivalence of protocols with method or property members
|
||||||
|
|
||||||
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
||||||
|
|
|
@ -49,6 +49,12 @@ pub struct AstNodeRef<T> {
|
||||||
_node: PhantomData<T>,
|
_node: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> AstNodeRef<T> {
|
||||||
|
pub(crate) fn index(&self) -> NodeIndex {
|
||||||
|
self.index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> AstNodeRef<T>
|
impl<T> AstNodeRef<T>
|
||||||
where
|
where
|
||||||
T: HasNodeIndex + Ranged + PartialEq + Debug,
|
T: HasNodeIndex + Ranged + PartialEq + Debug,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use ruff_python_ast::{HasNodeIndex, NodeIndex};
|
use ruff_python_ast::{HasNodeIndex, NodeIndex};
|
||||||
|
|
||||||
|
use crate::ast_node_ref::AstNodeRef;
|
||||||
|
|
||||||
/// Compact key for a node for use in a hash map.
|
/// Compact key for a node for use in a hash map.
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||||
pub(super) struct NodeKey(NodeIndex);
|
pub(super) struct NodeKey(NodeIndex);
|
||||||
|
@ -11,4 +13,8 @@ impl NodeKey {
|
||||||
{
|
{
|
||||||
NodeKey(node.node_index().load())
|
NodeKey(node.node_index().load())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_node_ref<T>(node_ref: &AstNodeRef<T>) -> Self {
|
||||||
|
NodeKey(node_ref.index())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,7 +159,6 @@ pub(crate) fn attribute_scopes<'db, 's>(
|
||||||
class_body_scope: ScopeId<'db>,
|
class_body_scope: ScopeId<'db>,
|
||||||
) -> impl Iterator<Item = FileScopeId> + use<'s, 'db> {
|
) -> impl Iterator<Item = FileScopeId> + use<'s, 'db> {
|
||||||
let file = class_body_scope.file(db);
|
let file = class_body_scope.file(db);
|
||||||
let module = parsed_module(db, file).load(db);
|
|
||||||
let index = semantic_index(db, file);
|
let index = semantic_index(db, file);
|
||||||
let class_scope_id = class_body_scope.file_scope_id(db);
|
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)
|
(child_scope_id, scope)
|
||||||
};
|
};
|
||||||
|
|
||||||
function_scope.node().as_function(&module)?;
|
function_scope.node().as_function()?;
|
||||||
Some(function_scope_id)
|
Some(function_scope_id)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -332,6 +331,39 @@ impl<'db> SemanticIndex<'db> {
|
||||||
Some(&self.scopes[self.parent_scope_id(scope_id)?])
|
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 {
|
fn is_scope_reachable(&self, db: &'db dyn Db, scope_id: FileScopeId) -> bool {
|
||||||
self.parent_scope_id(scope_id)
|
self.parent_scope_id(scope_id)
|
||||||
.is_none_or(|parent_scope_id| {
|
.is_none_or(|parent_scope_id| {
|
||||||
|
|
|
@ -2644,7 +2644,7 @@ impl SemanticSyntaxContext for SemanticIndexBuilder<'_, '_> {
|
||||||
match scope.kind() {
|
match scope.kind() {
|
||||||
ScopeKind::Class | ScopeKind::Lambda => return false,
|
ScopeKind::Class | ScopeKind::Lambda => return false,
|
||||||
ScopeKind::Function => {
|
ScopeKind::Function => {
|
||||||
return scope.node().expect_function(self.module).is_async;
|
return scope.node().expect_function().node(self.module).is_async;
|
||||||
}
|
}
|
||||||
ScopeKind::Comprehension
|
ScopeKind::Comprehension
|
||||||
| ScopeKind::Module
|
| ScopeKind::Module
|
||||||
|
|
|
@ -1227,3 +1227,12 @@ impl From<&ast::TypeParamTypeVarTuple> for DefinitionNodeKey {
|
||||||
Self(NodeKey::from_node(value))
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -397,52 +397,38 @@ impl NodeWithScopeKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_class<'ast>(
|
pub(crate) fn as_class(&self) -> Option<&AstNodeRef<ast::StmtClassDef>> {
|
||||||
&self,
|
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> &'ast ast::StmtClassDef {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Class(class) => class.node(module),
|
Self::Class(class) => Some(class),
|
||||||
_ => 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)),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_function<'ast>(
|
pub(crate) fn expect_class(&self) -> &AstNodeRef<ast::StmtClassDef> {
|
||||||
&self,
|
self.as_class().expect("expected class")
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> &'ast ast::StmtFunctionDef {
|
|
||||||
self.as_function(module).expect("expected function")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn expect_type_alias<'ast>(
|
pub(crate) fn as_function(&self) -> Option<&AstNodeRef<ast::StmtFunctionDef>> {
|
||||||
&self,
|
|
||||||
module: &'ast ParsedModuleRef,
|
|
||||||
) -> &'ast ast::StmtTypeAlias {
|
|
||||||
match self {
|
match self {
|
||||||
Self::TypeAlias(type_alias) => type_alias.node(module),
|
Self::Function(function) => Some(function),
|
||||||
_ => 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)),
|
|
||||||
_ => None,
|
_ => 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)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, get_size2::GetSize)]
|
||||||
|
|
|
@ -5649,7 +5649,7 @@ impl<'db> Type<'db> {
|
||||||
SpecialFormType::TypingSelf => {
|
SpecialFormType::TypingSelf => {
|
||||||
let module = parsed_module(db, scope_id.file(db)).load(db);
|
let module = parsed_module(db, scope_id.file(db)).load(db);
|
||||||
let index = semantic_index(db, scope_id.file(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 {
|
return Err(InvalidTypeExpressionError {
|
||||||
fallback_type: Type::unknown(),
|
fallback_type: Type::unknown(),
|
||||||
invalid_expressions: smallvec::smallvec_inline![
|
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> {
|
impl<'db> PEP695TypeAliasType<'db> {
|
||||||
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
let scope = self.rhs_scope(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();
|
||||||
let type_alias_stmt_node = scope.node(db).expect_type_alias(&module);
|
|
||||||
|
|
||||||
semantic_index(db, scope.file(db)).expect_single_definition(type_alias_stmt_node)
|
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> {
|
pub(crate) fn value_type(self, db: &'db dyn Db) -> Type<'db> {
|
||||||
let scope = self.rhs_scope(db);
|
let scope = self.rhs_scope(db);
|
||||||
let module = parsed_module(db, scope.file(db)).load(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);
|
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 {
|
fn normalized_impl(self, _db: &'db dyn Db, _visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
|
|
|
@ -1403,7 +1403,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
let scope = self.body_scope(db);
|
let scope = self.body_scope(db);
|
||||||
let file = scope.file(db);
|
let file = scope.file(db);
|
||||||
let parsed = parsed_module(db, file).load(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| {
|
class_def_node.type_params.as_ref().map(|type_params| {
|
||||||
let index = semantic_index(db, scope.file(db));
|
let index = semantic_index(db, scope.file(db));
|
||||||
let definition = index.expect_single_definition(class_def_node);
|
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!).
|
/// query depends on the AST of another file (bad!).
|
||||||
fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef {
|
fn node<'ast>(self, db: &'db dyn Db, module: &'ast ParsedModuleRef) -> &'ast ast::StmtClassDef {
|
||||||
let scope = self.body_scope(db);
|
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> {
|
pub(crate) fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
let body_scope = self.body_scope(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));
|
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(
|
pub(crate) fn apply_specialization(
|
||||||
|
@ -2870,8 +2869,8 @@ impl<'db> ClassLiteral<'db> {
|
||||||
let class_table = place_table(db, class_body_scope);
|
let class_table = place_table(db, class_body_scope);
|
||||||
|
|
||||||
let is_valid_scope = |method_scope: ScopeId<'db>| {
|
let is_valid_scope = |method_scope: ScopeId<'db>| {
|
||||||
if let Some(method_def) = method_scope.node(db).as_function(&module) {
|
if let Some(method_def) = method_scope.node(db).as_function() {
|
||||||
let method_name = method_def.name.as_str();
|
let method_name = method_def.node(&module).name.as_str();
|
||||||
if let Place::Type(Type::FunctionLiteral(method_type), _) =
|
if let Place::Type(Type::FunctionLiteral(method_type), _) =
|
||||||
class_symbol(db, class_body_scope, method_name).place
|
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
|
// The attribute assignment inherits the reachability of the method which contains it
|
||||||
let is_method_reachable =
|
let is_method_reachable = if let Some(method_def) = method_scope.node(db).as_function()
|
||||||
if let Some(method_def) = method_scope.node(db).as_function(&module) {
|
{
|
||||||
let method = index.expect_single_definition(method_def);
|
let method = index.expect_single_definition(method_def);
|
||||||
let method_place = class_table.symbol_id(&method_def.name).unwrap();
|
let method_place = class_table
|
||||||
class_map
|
.symbol_id(&method_def.node(&module).name)
|
||||||
.all_reachable_symbol_bindings(method_place)
|
.unwrap();
|
||||||
.find_map(|bind| {
|
class_map
|
||||||
(bind.binding.is_defined_and(|def| def == method))
|
.all_reachable_symbol_bindings(method_place)
|
||||||
.then(|| class_map.binding_reachability(db, &bind))
|
.find_map(|bind| {
|
||||||
})
|
(bind.binding.is_defined_and(|def| def == method))
|
||||||
.unwrap_or(Truthiness::AlwaysFalse)
|
.then(|| class_map.binding_reachability(db, &bind))
|
||||||
} else {
|
})
|
||||||
Truthiness::AlwaysFalse
|
.unwrap_or(Truthiness::AlwaysFalse)
|
||||||
};
|
} else {
|
||||||
|
Truthiness::AlwaysFalse
|
||||||
|
};
|
||||||
if is_method_reachable.is_always_false() {
|
if is_method_reachable.is_always_false() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -3323,7 +3324,7 @@ impl<'db> ClassLiteral<'db> {
|
||||||
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
|
pub(super) fn header_range(self, db: &'db dyn Db) -> TextRange {
|
||||||
let class_scope = self.body_scope(db);
|
let class_scope = self.body_scope(db);
|
||||||
let module = parsed_module(db, class_scope.file(db)).load(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;
|
let class_name = &class_node.name;
|
||||||
TextRange::new(
|
TextRange::new(
|
||||||
class_name.start(),
|
class_name.start(),
|
||||||
|
@ -4784,8 +4785,7 @@ impl KnownClass {
|
||||||
// 2. The first parameter of the current function (typically `self` or `cls`)
|
// 2. The first parameter of the current function (typically `self` or `cls`)
|
||||||
match overload.parameter_types() {
|
match overload.parameter_types() {
|
||||||
[] => {
|
[] => {
|
||||||
let Some(enclosing_class) =
|
let Some(enclosing_class) = nearest_enclosing_class(db, index, scope)
|
||||||
nearest_enclosing_class(db, index, scope, module)
|
|
||||||
else {
|
else {
|
||||||
BoundSuperError::UnavailableImplicitArguments
|
BoundSuperError::UnavailableImplicitArguments
|
||||||
.report_diagnostic(context, call_expression.into());
|
.report_diagnostic(context, call_expression.into());
|
||||||
|
|
|
@ -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.
|
// Inspect all ancestor function scopes by walking bottom up and infer the function's type.
|
||||||
let mut function_scope_tys = index
|
let mut function_scope_tys = index
|
||||||
.ancestor_scopes(scope_id)
|
.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)))
|
.map(|node| binding_type(self.db, index.expect_single_definition(node)))
|
||||||
.filter_map(Type::into_function_literal);
|
.filter_map(Type::into_function_literal);
|
||||||
|
|
||||||
|
|
|
@ -200,15 +200,15 @@ impl ClassDisplay<'_> {
|
||||||
|
|
||||||
match ancestor_scope.kind() {
|
match ancestor_scope.kind() {
|
||||||
ScopeKind::Class => {
|
ScopeKind::Class => {
|
||||||
if let Some(class_def) = node.as_class(&module_ast) {
|
if let Some(class_def) = node.as_class() {
|
||||||
name_parts.push(class_def.name.as_str().to_string());
|
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ScopeKind::Function => {
|
ScopeKind::Function => {
|
||||||
if let Some(function_def) = node.as_function(&module_ast) {
|
if let Some(function_def) = node.as_function() {
|
||||||
name_parts.push(format!(
|
name_parts.push(format!(
|
||||||
"<locals of function '{}'>",
|
"<locals of function '{}'>",
|
||||||
function_def.name.as_str()
|
function_def.node(&module_ast).name.as_str()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,7 +55,7 @@ use bitflags::bitflags;
|
||||||
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
|
use ruff_db::diagnostic::{Annotation, DiagnosticId, Severity, Span};
|
||||||
use ruff_db::files::{File, FileRange};
|
use ruff_db::files::{File, FileRange};
|
||||||
use ruff_db::parsed::{ParsedModuleRef, parsed_module};
|
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 ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::module_resolver::{KnownModule, file_to_module};
|
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::ast_ids::HasScopedUseId;
|
||||||
use crate::semantic_index::definition::Definition;
|
use crate::semantic_index::definition::Definition;
|
||||||
use crate::semantic_index::scope::ScopeId;
|
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::call::{Binding, CallArguments};
|
||||||
use crate::types::constraints::{ConstraintSet, Constraints};
|
use crate::types::constraints::{ConstraintSet, Constraints};
|
||||||
use crate::types::context::InferContext;
|
use crate::types::context::InferContext;
|
||||||
|
@ -80,7 +80,7 @@ use crate::types::{
|
||||||
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
|
BoundMethodType, BoundTypeVarInstance, CallableType, ClassBase, ClassLiteral, ClassType,
|
||||||
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor, HasRelationToVisitor,
|
||||||
IsEquivalentVisitor, KnownClass, NormalizedVisitor, SpecialFormType, Truthiness, Type,
|
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};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
@ -236,6 +236,22 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
self.has_known_decorator(db, FunctionDecorators::OVERLOAD)
|
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>(
|
fn node<'ast>(
|
||||||
self,
|
self,
|
||||||
db: &dyn Db,
|
db: &dyn Db,
|
||||||
|
@ -249,7 +265,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
the function is defined."
|
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.
|
/// Returns the [`FileRange`] of the function's name.
|
||||||
|
@ -258,7 +274,8 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
self.file(db),
|
self.file(db),
|
||||||
self.body_scope(db)
|
self.body_scope(db)
|
||||||
.node(db)
|
.node(db)
|
||||||
.expect_function(module)
|
.expect_function()
|
||||||
|
.node(module)
|
||||||
.name
|
.name
|
||||||
.range,
|
.range,
|
||||||
)
|
)
|
||||||
|
@ -274,9 +291,8 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
/// over-invalidation.
|
/// over-invalidation.
|
||||||
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
fn definition(self, db: &'db dyn Db) -> Definition<'db> {
|
||||||
let body_scope = self.body_scope(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));
|
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
|
/// 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
|
let use_id = self
|
||||||
.body_scope(db)
|
.body_scope(db)
|
||||||
.node(db)
|
.node(db)
|
||||||
.expect_function(&module)
|
.expect_function()
|
||||||
|
.node(&module)
|
||||||
.name
|
.name
|
||||||
.scoped_use_id(db, scope);
|
.scoped_use_id(db, scope);
|
||||||
|
|
||||||
|
@ -325,17 +342,79 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
inherited_generic_context: Option<GenericContext<'db>>,
|
inherited_generic_context: Option<GenericContext<'db>>,
|
||||||
) -> Signature<'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 scope = self.body_scope(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(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 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 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)
|
GenericContext::from_type_params(db, index, definition, type_params)
|
||||||
});
|
});
|
||||||
|
let file_scope_id = scope.file_scope_id(db);
|
||||||
let index = semantic_index(db, scope.file(db));
|
let is_generator = file_scope_id.is_generator_function(index);
|
||||||
let is_generator = scope.file_scope_id(db).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(
|
Signature::from_function(
|
||||||
db,
|
db,
|
||||||
|
@ -344,6 +423,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
definition,
|
definition,
|
||||||
function_stmt_node,
|
function_stmt_node,
|
||||||
is_generator,
|
is_generator,
|
||||||
|
has_implicitly_positional_first_parameter,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +436,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
let span = Span::from(function_scope.file(db));
|
let span = Span::from(function_scope.file(db));
|
||||||
let node = function_scope.node(db);
|
let node = function_scope.node(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(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
|
let range = parameter_index
|
||||||
.and_then(|parameter_index| {
|
.and_then(|parameter_index| {
|
||||||
func_def
|
func_def
|
||||||
|
@ -376,7 +456,7 @@ impl<'db> OverloadLiteral<'db> {
|
||||||
let span = Span::from(function_scope.file(db));
|
let span = Span::from(function_scope.file(db));
|
||||||
let node = function_scope.node(db);
|
let node = function_scope.node(db);
|
||||||
let module = parsed_module(db, self.file(db)).load(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 return_type_range = func_def.returns.as_ref().map(|returns| returns.range());
|
||||||
let mut signature = func_def.name.range.cover(func_def.parameters.range);
|
let mut signature = func_def.name.range.cover(func_def.parameters.range);
|
||||||
if let Some(return_type_range) = return_type_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
|
/// Returns true if this method is decorated with `@classmethod`, or if it is implicitly a
|
||||||
/// classmethod.
|
/// classmethod.
|
||||||
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
|
pub(crate) fn is_classmethod(self, db: &'db dyn Db) -> bool {
|
||||||
self.has_known_decorator(db, FunctionDecorators::CLASSMETHOD)
|
self.iter_overloads_and_implementation(db)
|
||||||
|| matches!(
|
.any(|overload| overload.is_classmethod(db))
|
||||||
self.name(db).as_str(),
|
|
||||||
"__init_subclass__" | "__class_getitem__"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
|
/// Returns true if this method is decorated with `@staticmethod`, or if it is implicitly a
|
||||||
/// static method.
|
/// static method.
|
||||||
pub(crate) fn is_staticmethod(self, db: &'db dyn Db) -> bool {
|
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`.
|
/// If the implementation of this function is deprecated, returns the `@warnings.deprecated`.
|
||||||
|
|
|
@ -422,12 +422,11 @@ pub(crate) fn nearest_enclosing_class<'db>(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
semantic: &SemanticIndex<'db>,
|
semantic: &SemanticIndex<'db>,
|
||||||
scope: ScopeId,
|
scope: ScopeId,
|
||||||
parsed: &ParsedModuleRef,
|
|
||||||
) -> Option<ClassLiteral<'db>> {
|
) -> Option<ClassLiteral<'db>> {
|
||||||
semantic
|
semantic
|
||||||
.ancestor_scopes(scope.file_scope_id(db))
|
.ancestor_scopes(scope.file_scope_id(db))
|
||||||
.find_map(|(_, ancestor_scope)| {
|
.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);
|
let definition = semantic.expect_single_definition(class);
|
||||||
infer_definition_types(db, definition)
|
infer_definition_types(db, definition)
|
||||||
.declaration_type(definition)
|
.declaration_type(definition)
|
||||||
|
@ -2418,29 +2417,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
/// behaviour to the [`nearest_enclosing_class`] function.
|
/// behaviour to the [`nearest_enclosing_class`] function.
|
||||||
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
|
fn class_context_of_current_method(&self) -> Option<ClassType<'db>> {
|
||||||
let current_scope_id = self.scope().file_scope_id(self.db());
|
let current_scope_id = self.scope().file_scope_id(self.db());
|
||||||
let current_scope = self.index.scope(current_scope_id);
|
let class_definition = self.index.class_definition_of_method(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);
|
|
||||||
binding_type(self.db(), class_definition).to_class_type(self.db())
|
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() {
|
if !current_scope.kind().is_non_lambda_function() {
|
||||||
return None;
|
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>(
|
fn function_decorator_types<'a>(
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
use std::{collections::HashMap, slice::Iter};
|
use std::{collections::HashMap, slice::Iter};
|
||||||
|
|
||||||
use itertools::EitherOrBoth;
|
use itertools::{EitherOrBoth, Itertools};
|
||||||
use smallvec::{SmallVec, smallvec_inline};
|
use smallvec::{SmallVec, smallvec_inline};
|
||||||
|
|
||||||
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
|
use super::{DynamicType, Type, TypeVarVariance, definition_expression_type};
|
||||||
|
@ -352,9 +352,14 @@ impl<'db> Signature<'db> {
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
function_node: &ast::StmtFunctionDef,
|
function_node: &ast::StmtFunctionDef,
|
||||||
is_generator: bool,
|
is_generator: bool,
|
||||||
|
has_implicitly_positional_first_parameter: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let parameters =
|
let parameters = Parameters::from_parameters(
|
||||||
Parameters::from_parameters(db, definition, function_node.parameters.as_ref());
|
db,
|
||||||
|
definition,
|
||||||
|
function_node.parameters.as_ref(),
|
||||||
|
has_implicitly_positional_first_parameter,
|
||||||
|
);
|
||||||
let return_ty = function_node.returns.as_ref().map(|returns| {
|
let return_ty = function_node.returns.as_ref().map(|returns| {
|
||||||
let plain_return_ty = definition_expression_type(db, definition, returns.as_ref())
|
let plain_return_ty = definition_expression_type(db, definition, returns.as_ref())
|
||||||
.apply_type_mapping(
|
.apply_type_mapping(
|
||||||
|
@ -1139,6 +1144,7 @@ impl<'db> Parameters<'db> {
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
definition: Definition<'db>,
|
definition: Definition<'db>,
|
||||||
parameters: &ast::Parameters,
|
parameters: &ast::Parameters,
|
||||||
|
has_implicitly_positional_first_parameter: bool,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let ast::Parameters {
|
let ast::Parameters {
|
||||||
posonlyargs,
|
posonlyargs,
|
||||||
|
@ -1149,23 +1155,46 @@ impl<'db> Parameters<'db> {
|
||||||
range: _,
|
range: _,
|
||||||
node_index: _,
|
node_index: _,
|
||||||
} = parameters;
|
} = parameters;
|
||||||
|
|
||||||
let default_type = |param: &ast::ParameterWithDefault| {
|
let default_type = |param: &ast::ParameterWithDefault| {
|
||||||
param
|
param
|
||||||
.default()
|
.default()
|
||||||
.map(|default| definition_expression_type(db, definition, 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(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
definition,
|
definition,
|
||||||
&arg.parameter,
|
¶m.parameter,
|
||||||
ParameterKind::PositionalOnly {
|
ParameterKind::PositionalOnly {
|
||||||
name: Some(arg.parameter.name.id.clone()),
|
name: Some(param.parameter.name.id.clone()),
|
||||||
default_type: default_type(arg),
|
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(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
definition,
|
definition,
|
||||||
|
@ -1176,6 +1205,7 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let variadic = vararg.as_ref().map(|arg| {
|
let variadic = vararg.as_ref().map(|arg| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
|
@ -1186,6 +1216,7 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let keyword_only = kwonlyargs.iter().map(|arg| {
|
let keyword_only = kwonlyargs.iter().map(|arg| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
|
@ -1197,6 +1228,7 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let keywords = kwarg.as_ref().map(|arg| {
|
let keywords = kwarg.as_ref().map(|arg| {
|
||||||
Parameter::from_node_and_kind(
|
Parameter::from_node_and_kind(
|
||||||
db,
|
db,
|
||||||
|
@ -1207,8 +1239,10 @@ impl<'db> Parameters<'db> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
Self::new(
|
Self::new(
|
||||||
positional_only
|
positional_only
|
||||||
|
.into_iter()
|
||||||
.chain(positional_or_keyword)
|
.chain(positional_or_keyword)
|
||||||
.chain(variadic)
|
.chain(variadic)
|
||||||
.chain(keyword_only)
|
.chain(keyword_only)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue