mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-30 05:44:56 +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
|
@ -68,6 +68,78 @@ def _(flag: bool):
|
|||
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
|
||||
|
||||
### 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
|
||||
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`.
|
||||
|
||||
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 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)
|
||||
# revealed: {"__index__": MethodMember(`(self) -> int`)}
|
||||
# revealed: {"__index__": MethodMember(`(self, /) -> int`)}
|
||||
reveal_protocol_interface(SupportsIndex)
|
||||
# revealed: {"__abs__": MethodMember(`(self) -> Unknown`)}
|
||||
# revealed: {"__abs__": MethodMember(`(self, /) -> Unknown`)}
|
||||
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)
|
||||
|
||||
# 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(Iterator[int])) # revealed: frozenset[str]
|
||||
|
||||
# revealed: {"__abs__": MethodMember(`(self) -> int`)}
|
||||
# revealed: {"__abs__": MethodMember(`(self, /) -> 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])
|
||||
|
||||
class BaseProto(Protocol):
|
||||
|
@ -450,10 +450,10 @@ class BaseProto(Protocol):
|
|||
class SubProto(BaseProto, Protocol):
|
||||
def member(self) -> bool: ...
|
||||
|
||||
# revealed: {"member": MethodMember(`(self) -> int`)}
|
||||
# revealed: {"member": MethodMember(`(self, /) -> int`)}
|
||||
reveal_protocol_interface(BaseProto)
|
||||
|
||||
# revealed: {"member": MethodMember(`(self) -> bool`)}
|
||||
# revealed: {"member": MethodMember(`(self, /) -> bool`)}
|
||||
reveal_protocol_interface(SubProto)
|
||||
|
||||
class ProtoWithClassVar(Protocol):
|
||||
|
@ -1767,7 +1767,7 @@ class Foo(Protocol):
|
|||
def method(self) -> str: ...
|
||||
|
||||
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:
|
||||
def __init__(self):
|
||||
|
@ -1776,6 +1776,31 @@ class Bar:
|
|||
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
|
||||
|
||||
Two protocols `P1` and `P2`, both with a method member `x`, are considered equivalent if the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue