mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 02:13:08 +00:00

## Summary Add support for decorators on function as well as support for properties by adding special handling for `@property` and `@<name of property>.setter`/`.getter` decorators. closes https://github.com/astral-sh/ruff/issues/16987 ## Ecosystem results - ✔️ A lot of false positives are fixed by our new understanding of properties - 🔴 A bunch of new false positives (typically `possibly-unbound-attribute` or `invalid-argument-type`) occur because we currently do not perform type narrowing on attributes. And with the new understanding of properties, this becomes even more relevant. In many cases, the narrowing occurs through an assertion, so this is also something that we need to implement to get rid of these false positives. - 🔴 A few new false positives occur because we do not understand generics, and therefore some calls to custom setters fail. - 🔴 Similarly, some false positives occur because we do not understand protocols yet. - ✔️ Seems like a true positive to me. [The setter](e624d8edfa/src/packaging/specifiers.py (L752-L754)
) only accepts `bools`, but `None` is assigned in [this line](e624d8edfa/tests/test_specifiers.py (L688)
). ``` + error[lint:invalid-assignment] /tmp/mypy_primer/projects/packaging/tests/test_specifiers.py:688:9: Invalid assignment to data descriptor attribute `prereleases` on type `SpecifierSet` with custom `__set__` method ``` - ✔️ This is arguable also a true positive. The setter [here](0c6c75644f/rich/table.py (L359-L363)
) returns `Table`, but typeshed wants [setters to return `None`](bf8d2a9912/stdlib/builtins.pyi (L1298)
). ``` + error[lint:invalid-argument-type] /tmp/mypy_primer/projects/rich/rich/table.py:359:5: Object of type `Literal[padding]` cannot be assigned to parameter 2 (`fset`) of bound method `setter`; expected type `(Any, Any, /) -> None` ``` ## Follow ups - Fix the `@no_type_check` regression - Implement class decorators ## Test Plan New Markdown test suites for decorators and properties.
7 KiB
7 KiB
Call expression
Simple
def get_int() -> int:
return 42
reveal_type(get_int()) # revealed: int
Async
async def get_int_async() -> int:
return 42
# TODO: we don't yet support `types.CoroutineType`, should be generic `Coroutine[Any, Any, int]`
reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType)
Generic
def get_int[T]() -> int:
return 42
reveal_type(get_int()) # revealed: int
Decorated
from typing import Callable
def foo() -> int:
return 42
def decorator(func) -> Callable[[], int]:
return foo
@decorator
def bar() -> str:
return "bar"
reveal_type(bar()) # revealed: int
Invalid callable
nonsense = 123
x = nonsense() # error: "Object of type `Literal[123]` is not callable"
Potentially unbound function
def _(flag: bool):
if flag:
def foo() -> int:
return 42
# error: [possibly-unresolved-reference]
reveal_type(foo()) # revealed: int
Wrong argument type
Positional argument, positional-or-keyword parameter
def f(x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 1 (`x`) of function `f`; expected type `int`"
reveal_type(f("foo")) # revealed: int
Positional argument, positional-only parameter
def f(x: int, /) -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter 1 (`x`) of function `f`; expected type `int`"
reveal_type(f("foo")) # revealed: int
Positional argument, variadic parameter
def f(*args: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter `*args` of function `f`; expected type `int`"
reveal_type(f("foo")) # revealed: int
Keyword argument, positional-or-keyword parameter
def f(x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter `x` of function `f`; expected type `int`"
reveal_type(f(x="foo")) # revealed: int
Keyword argument, keyword-only parameter
def f(*, x: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter `x` of function `f`; expected type `int`"
reveal_type(f(x="foo")) # revealed: int
Keyword argument, keywords parameter
def f(**kwargs: int) -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal["foo"]` cannot be assigned to parameter `**kwargs` of function `f`; expected type `int`"
reveal_type(f(x="foo")) # revealed: int
Correctly match keyword out-of-order
def f(x: int = 1, y: str = "foo") -> int:
return 1
# error: 15 [invalid-argument-type] "Object of type `Literal[2]` cannot be assigned to parameter `y` of function `f`; expected type `str`"
# error: 20 [invalid-argument-type] "Object of type `Literal["bar"]` cannot be assigned to parameter `x` of function `f`; expected type `int`"
reveal_type(f(y=2, x="bar")) # revealed: int
Too many positional arguments
One too many
def f() -> int:
return 1
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 1"
reveal_type(f("foo")) # revealed: int
Two too many
def f() -> int:
return 1
# error: 15 [too-many-positional-arguments] "Too many positional arguments to function `f`: expected 0, got 2"
reveal_type(f("foo", "bar")) # revealed: int
No too-many-positional if variadic is taken
def f(*args: int) -> int:
return 1
reveal_type(f(1, 2, 3)) # revealed: int
Multiple keyword arguments map to keyword variadic parameter
def f(**kwargs: int) -> int:
return 1
reveal_type(f(foo=1, bar=2)) # revealed: int
Missing arguments
No defaults or variadic
def f(x: int) -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
With default
def f(x: int, y: str = "foo") -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
Defaulted argument is not required
def f(x: int = 1) -> int:
return 1
reveal_type(f()) # revealed: int
With variadic
def f(x: int, *y: str) -> int:
return 1
# error: 13 [missing-argument] "No argument provided for required parameter `x` of function `f`"
reveal_type(f()) # revealed: int
Variadic argument is not required
def f(*args: int) -> int:
return 1
reveal_type(f()) # revealed: int
Keywords argument is not required
def f(**kwargs: int) -> int:
return 1
reveal_type(f()) # revealed: int
Multiple
def f(x: int, y: int) -> int:
return 1
# error: 13 [missing-argument] "No arguments provided for required parameters `x`, `y` of function `f`"
reveal_type(f()) # revealed: int
Unknown argument
def f(x: int) -> int:
return 1
# error: 20 [unknown-argument] "Argument `y` does not match any known parameter of function `f`"
reveal_type(f(x=1, y=2)) # revealed: int
Parameter already assigned
def f(x: int) -> int:
return 1
# error: 18 [parameter-already-assigned] "Multiple values provided for parameter `x` of function `f`"
reveal_type(f(1, x=2)) # revealed: int
Special functions
Some functions require special handling in type inference. Here, we make sure that we still emit proper diagnostics in case of missing or superfluous arguments.
reveal_type
from typing_extensions import reveal_type
# error: [missing-argument] "No argument provided for required parameter `obj` of function `reveal_type`"
reveal_type()
# error: [too-many-positional-arguments] "Too many positional arguments to function `reveal_type`: expected 1, got 2"
reveal_type(1, 2)
static_assert
from knot_extensions import static_assert
# error: [missing-argument] "No argument provided for required parameter `condition` of function `static_assert`"
static_assert()
# error: [too-many-positional-arguments] "Too many positional arguments to function `static_assert`: expected 2, got 3"
static_assert(True, 2, 3)
len
# error: [missing-argument] "No argument provided for required parameter `obj` of function `len`"
len()
# error: [too-many-positional-arguments] "Too many positional arguments to function `len`: expected 1, got 2"
len([], 1)
Type API predicates
from knot_extensions import is_subtype_of, is_fully_static
# error: [missing-argument]
is_subtype_of()
# error: [missing-argument]
is_subtype_of(int)
# error: [too-many-positional-arguments]
is_subtype_of(int, int, int)
# error: [too-many-positional-arguments]
is_subtype_of(int, int, int, int)
# error: [missing-argument]
is_fully_static()
# error: [too-many-positional-arguments]
is_fully_static(int, int)