mirror of
https://github.com/astral-sh/ruff.git
synced 2026-10-28 10:45:37 +08:00
[ty] Fix variadic signature display (#27869)
## Summary Preserve the unpacking `*` in named variadic annotations and avoid adding a redundant bare `*` before keyword-only parameters when `*args` is already present. This fixes malformed revealed signatures without changing signature storage or inference. ## Test plan - Add a directly declared mixed-signature regression. - Update TypeVarTuple partial, lambda, and bidirectional reveal expectations. - Regenerate existing single-line and multiline signature-display snapshots.
This commit is contained in:
1 parent
49bf50b1c7
commit
c876a953dd
6 files changed
+51
-25
No files matched your search
@@ -6018,7 +6018,7 @@ Source with applied edits:
|
||||
|
||||
def foo(x: int, *y: bool, z: str | int | list[str]): ...
|
||||
|
||||
a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
---------------------------------------------
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:LL:7
|
||||
@@ -6028,7 +6028,7 @@ Source with applied edits:
|
||||
info: Source
|
||||
--> main2.py:LL:16
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -6039,7 +6039,7 @@ Source with applied edits:
|
||||
info: Source
|
||||
--> main2.py:LL:25
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^^
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
@@ -6048,10 +6048,10 @@ Source with applied edits:
|
||||
LL | class str(Sequence[str]):
|
||||
| ^^^
|
||||
info: Source
|
||||
--> main2.py:LL:37
|
||||
--> main2.py:LL:34
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:LL:7
|
||||
@@ -6059,10 +6059,10 @@ Source with applied edits:
|
||||
LL | class int:
|
||||
| ^^^
|
||||
info: Source
|
||||
--> main2.py:LL:43
|
||||
--> main2.py:LL:40
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:LL:7
|
||||
@@ -6070,10 +6070,10 @@ Source with applied edits:
|
||||
LL | class list(MutableSequence[_T]):
|
||||
| ^^^^
|
||||
info: Source
|
||||
--> main2.py:LL:49
|
||||
--> main2.py:LL:46
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^^
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^^
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/builtins.pyi:LL:7
|
||||
@@ -6081,10 +6081,10 @@ Source with applied edits:
|
||||
LL | class str(Sequence[str]):
|
||||
| ^^^
|
||||
info: Source
|
||||
--> main2.py:LL:54
|
||||
--> main2.py:LL:51
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^
|
||||
|
||||
info[inlay-hint-location]: Inlay Hint Target
|
||||
--> stdlib/ty_extensions/_internal.pyi:LL:1
|
||||
@@ -6092,10 +6092,10 @@ Source with applied edits:
|
||||
LL | Unknown: _SpecialForm
|
||||
| ^^^^^^^
|
||||
info: Source
|
||||
--> main2.py:LL:63
|
||||
--> main2.py:LL:60
|
||||
|
|
||||
LL | a[: def foo(x: int, *y: bool, *, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^^^^^
|
||||
LL | a[: def foo(x: int, *y: bool, z: str | int | list[str]) -> Unknown] = foo
|
||||
| ^^^^^^^
|
||||
");
|
||||
}
|
||||
|
||||
|
||||
@@ -1804,7 +1804,7 @@ reveal_type(f8) # revealed: (int, /) -> None
|
||||
# An optional keyword-only parameter does not prevent `*args` from accepting the positional
|
||||
# suffix in a `Callable` annotation.
|
||||
f9: Callable[[*tuple[int, ...], int], None] = lambda *args, x=1: None
|
||||
reveal_type(f9) # revealed: (*args, *, x=1) -> None
|
||||
reveal_type(f9) # revealed: (*args, x=1) -> None
|
||||
|
||||
f10: Callable[[str, int, str], tuple[str, int, str]] = lambda x, y, z: reveal_type((x, y, z)) # revealed: tuple[str, int, str]
|
||||
reveal_type(f10) # revealed: (x: str, y: int, z: str) -> tuple[str, int, str]
|
||||
|
||||
@@ -410,7 +410,7 @@ def collect[*Ts](*values: *Ts) -> tuple[*Ts]:
|
||||
return values
|
||||
|
||||
bound = partial(collect)
|
||||
reveal_type(bound) # revealed: partial[[*Ts](*values: Ts) -> tuple[*Ts]]
|
||||
reveal_type(bound) # revealed: partial[[*Ts](*values: *Ts) -> tuple[*Ts]]
|
||||
reveal_type(bound()) # revealed: tuple[()]
|
||||
reveal_type(bound("x", 1)) # revealed: tuple[Literal["x"], Literal[1]]
|
||||
```
|
||||
@@ -432,7 +432,7 @@ def collect[*Ts](prefix: int, *values: *Ts) -> tuple[*Ts]:
|
||||
return values
|
||||
|
||||
bound = partial(collect, 1)
|
||||
reveal_type(bound) # revealed: partial[[*Ts](*values: Ts) -> tuple[*Ts]]
|
||||
reveal_type(bound) # revealed: partial[[*Ts](*values: *Ts) -> tuple[*Ts]]
|
||||
reveal_type(bound()) # revealed: tuple[()]
|
||||
reveal_type(bound("x", 2)) # revealed: tuple[Literal["x"], Literal[2]]
|
||||
reveal_type(collect(1)) # revealed: tuple[()]
|
||||
@@ -455,7 +455,7 @@ def collect[T, *Ts](prefix: T, *values: *Ts) -> tuple[T, *Ts]:
|
||||
return (prefix, *values)
|
||||
|
||||
bound = partial(collect, 1)
|
||||
reveal_type(bound) # revealed: partial[[*Ts](*values: Ts) -> tuple[Literal[1], *Ts]]
|
||||
reveal_type(bound) # revealed: partial[[*Ts](*values: *Ts) -> tuple[Literal[1], *Ts]]
|
||||
reveal_type(bound()) # revealed: tuple[Literal[1]]
|
||||
reveal_type(bound("x", True)) # revealed: tuple[Literal[1], Literal["x"], Literal[True]]
|
||||
```
|
||||
|
||||
@@ -55,7 +55,7 @@ reveal_type(lambda **kwargs: kwargs) # revealed: (**kwargs) -> dict[str, Unknow
|
||||
Mixing all of them together:
|
||||
|
||||
```py
|
||||
# revealed: (a, b, /, c=True, *args, *, d="default", e=5, **kwargs) -> None
|
||||
# revealed: (a, b, /, c=True, *args, d="default", e=5, **kwargs) -> None
|
||||
reveal_type(lambda a, b, /, c=True, *args, d="default", e=5, **kwargs: None)
|
||||
```
|
||||
|
||||
|
||||
@@ -77,6 +77,29 @@ def _(x: object):
|
||||
reveal_type(c) # revealed: C[Top[(...)]]
|
||||
```
|
||||
|
||||
## Unpacked variadic signatures
|
||||
|
||||
Display an unpacked variadic as one parameter, including its fixed prefix and required suffix.
|
||||
|
||||
```toml
|
||||
[environment]
|
||||
python-version = "3.12"
|
||||
```
|
||||
|
||||
```py
|
||||
def mixed(
|
||||
prefix: bytes,
|
||||
/,
|
||||
label: str,
|
||||
*args: *tuple[bool, *tuple[int, ...], bytes, str],
|
||||
flag: bool = False,
|
||||
**kwargs: bytes,
|
||||
) -> None: ...
|
||||
|
||||
# revealed: def mixed(prefix: bytes, /, label: str, *args: *tuple[bool, *tuple[int, ...], bytes, str], flag: bool = False, **kwargs: bytes) -> None
|
||||
reveal_type(mixed)
|
||||
```
|
||||
|
||||
## Type aliases are not expanded unless necessary
|
||||
|
||||
```toml
|
||||
|
||||
@@ -2610,6 +2610,7 @@ impl<'db> FmtDetailed<'db> for DisplayParameters<'_, 'db> {
|
||||
.fmt_detailed(&mut f.with_detail(TypeDetail::Parameter(param_name)))?;
|
||||
|
||||
after_synthetic_unpack |= is_synthetic_unpack;
|
||||
star_added |= parameter.is_variadic();
|
||||
first = false;
|
||||
}
|
||||
|
||||
@@ -2744,6 +2745,9 @@ impl<'db> FmtDetailed<'db> for DisplayParameter<'_, 'db> {
|
||||
if self.param.should_annotation_be_displayed() {
|
||||
let annotated_type = self.param.annotated_type();
|
||||
f.write_str(": ")?;
|
||||
if self.param.is_variadic() && self.param.has_starred_annotation() {
|
||||
f.write_char('*')?;
|
||||
}
|
||||
annotated_type
|
||||
.display_with(db, self.env, self.settings.clone())
|
||||
.fmt_detailed(f)?;
|
||||
@@ -4090,7 +4094,7 @@ mod tests {
|
||||
],
|
||||
Some(KnownClass::Bytes.to_instance(db, &env))
|
||||
),
|
||||
@"(a, b: int, c=1, d: int = 2, /, e=3, f: int = 4, *args: object, *, g=5, h: int = 6, **kwargs: str) -> bytes"
|
||||
@"(a, b: int, c=1, d: int = 2, /, e=3, f: int = 4, *args: object, g=5, h: int = 6, **kwargs: str) -> bytes"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4256,7 +4260,6 @@ mod tests {
|
||||
e=3,
|
||||
f: int = 4,
|
||||
*args: object,
|
||||
*,
|
||||
g=5,
|
||||
h: int = 6,
|
||||
**kwargs: str
|
||||
|
||||
Reference in new issue
Block a user