diff --git a/CHANGELOG.md b/CHANGELOG.md index 698e1f946f..ee2e13317c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,79 @@ # Changelog +## 0.0.1-alpha.28 + +Released on 2025-11-25. + +### Bug fixes + +- Fix panic for unclosed string literal in type annotation position ([#21592](https://github.com/astral-sh/ruff/pull/21592)) + +### LSP server + +- Improve go-to-definition and add go-to-definition for inlay hints + ([#21545](https://github.com/astral-sh/ruff/pull/21545), + [#21546](https://github.com/astral-sh/ruff/pull/21546), + [#21544](https://github.com/astral-sh/ruff/pull/21544), + [#21616](https://github.com/astral-sh/ruff/pull/21616), + [#21548](https://github.com/astral-sh/ruff/pull/21548)) +- Implement go-to-type for inlay type hints ([#21533](https://github.com/astral-sh/ruff/pull/21533)) +- Add "remove unused ignore comment" code action ([#21582](https://github.com/astral-sh/ruff/pull/21582)) +- Don't suggest completions that aren't subclasses of `BaseException` after `raise` ([#21571](https://github.com/astral-sh/ruff/pull/21571)) +- Implement double click to insert inlay hint ([#21600](https://github.com/astral-sh/ruff/pull/21600)) +- Fix edge cases for autocomplete suppressions in variable bindings ([#21576](https://github.com/astral-sh/ruff/pull/21576)) +- Implement docstring rendering to markdown ([#21550](https://github.com/astral-sh/ruff/pull/21550)) +- Support string annotations ([#21577](https://github.com/astral-sh/ruff/pull/21577)) +- Improve import detection for completions and support `from ...` completions ([#21547](https://github.com/astral-sh/ruff/pull/21547)) +- Improve handling of hover/goto on imports ([#21572](https://github.com/astral-sh/ruff/pull/21572)) +- Don't allow edits of some more invalid syntax types in inlay hints ([#21621](https://github.com/astral-sh/ruff/pull/21621)) +- Resolve applicable overloads for hover on an overloaded function call ([#21417](https://github.com/astral-sh/ruff/pull/21417)) +- Consistently add the `DEFINITION` modifier when computing semantic tokens ([#21521](https://github.com/astral-sh/ruff/pull/21521)) +- Suppress autocomplete suggestions during variable binding ([#21549](https://github.com/astral-sh/ruff/pull/21549)) + +### CLI + +- Exit with code `2` if there's any IO error ([#21508](https://github.com/astral-sh/ruff/pull/21508)) + +### Other changes + +- Add hint about resolved Python version when a user attempts to import a member added on a newer version ([#21615](https://github.com/astral-sh/ruff/pull/21615)) +- Attach subdiagnostics to `unresolved-import` errors for relative imports as well as absolute imports ([#21554](https://github.com/astral-sh/ruff/pull/21554)) +- Avoid expression re-inference for diagnostics ([#21267](https://github.com/astral-sh/ruff/pull/21267)) +- Check method definitions on subclasses for Liskov violations ([#21436](https://github.com/astral-sh/ruff/pull/21436)) +- Eagerly evaluate `types.UnionType` elements as type expressions ([#21531](https://github.com/astral-sh/ruff/pull/21531)) +- Extend Liskov checks to also cover classmethods and staticmethods ([#21598](https://github.com/astral-sh/ruff/pull/21598)) +- Fix rendering of unused suppression diagnostic ([#21580](https://github.com/astral-sh/ruff/pull/21580)) +- Implement `typing.override` ([#21627](https://github.com/astral-sh/ruff/pull/21627)) +- Improve concise diagnostics for invalid exceptions when a user catches a tuple of objects ([#21578](https://github.com/astral-sh/ruff/pull/21578)) +- Improve debug messages when imports fail ([#21555](https://github.com/astral-sh/ruff/pull/21555)) +- Improve diagnostics when `NotImplemented` is called ([#21523](https://github.com/astral-sh/ruff/pull/21523)) +- Improve diagnostics when a submodule is not available as an attribute on a module-literal type ([#21561](https://github.com/astral-sh/ruff/pull/21561)) +- Improve several "Did you mean?" suggestions ([#21597](https://github.com/astral-sh/ruff/pull/21597)) +- Narrow type context during literal promotion in generic class constructors ([#21574](https://github.com/astral-sh/ruff/pull/21574)) +- Retain the function-like-ness of `Callable` types when binding `self` ([#21614](https://github.com/astral-sh/ruff/pull/21614)) +- Substitute for `typing.Self` when checking protocol members ([#21569](https://github.com/astral-sh/ruff/pull/21569)) +- Switch the error code from `unresolved-attribute` to `possibly-missing-attribute` for submodules that may not be available ([#21618](https://github.com/astral-sh/ruff/pull/21618)) +- Implement `TypedDict` structural assignment ([#21467](https://github.com/astral-sh/ruff/pull/21467)) +- Make implicit submodule imports re-exported ([#21573](https://github.com/astral-sh/ruff/pull/21573)) +- Support PEP 613 `typing.TypeAlias` type aliases ([#21394](https://github.com/astral-sh/ruff/pull/21394)) +- Support generic aliases in `type[...]`, like `type[C[int]]` ([#21552](https://github.com/astral-sh/ruff/pull/21552)) +- Tighten up handling of subscripts in type expressions ([#21503](https://github.com/astral-sh/ruff/pull/21503)) + +### Contributors + +- [@Gankra](https://github.com/Gankra) +- [@MatthewMckee4](https://github.com/MatthewMckee4) +- [@AlexWaygood](https://github.com/AlexWaygood) +- [@RasmusNygren](https://github.com/RasmusNygren) +- [@dcreager](https://github.com/dcreager) +- [@BurntSushi](https://github.com/BurntSushi) +- [@carljm](https://github.com/carljm) +- [@MichaReiser](https://github.com/MichaReiser) +- [@sharkdp](https://github.com/sharkdp) +- [@oconnor663](https://github.com/oconnor663) +- [@lucach](https://github.com/lucach) +- [@ibraheemdev](https://github.com/ibraheemdev) + ## 0.0.1-alpha.27 Released on 2025-11-18. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3985fc721..59d0355323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -138,6 +138,8 @@ Releases can only be performed by Astral team members. Preparation for the release is automated. +1. Install the pre-commit hooks as described above, if you haven't already. + 1. Checkout the `main` branch and run `git pull origin main --recurse-submodules --tags`. 1. Create and checkout a new branch for the release. diff --git a/dist-workspace.toml b/dist-workspace.toml index 0f5d9cbfbd..1600666d94 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -1,7 +1,7 @@ [workspace] members = ["cargo:./ruff"] packages = ["ty"] -version = "0.0.1-alpha.27" +version = "0.0.1-alpha.28" # Config for 'dist' [dist] diff --git a/docs/reference/rules.md b/docs/reference/rules.md index 7131564c47..a6b571c402 100644 --- a/docs/reference/rules.md +++ b/docs/reference/rules.md @@ -39,7 +39,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -63,7 +63,7 @@ Calling a non-callable object will raise a `TypeError` at runtime. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -95,7 +95,7 @@ f(int) # error Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -126,7 +126,7 @@ a = 1 Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -158,7 +158,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -190,7 +190,7 @@ class B(A): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -217,7 +217,7 @@ class B(A, A): ... Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -329,7 +329,7 @@ def test(): -> "Literal[5]": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -359,7 +359,7 @@ class C(A, B): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -385,7 +385,7 @@ t[3] # IndexError: tuple index out of range Default level: error · Added in 0.0.1-alpha.12 · Related issues · -View source +View source @@ -474,7 +474,7 @@ an atypical memory layout. Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -501,7 +501,7 @@ func("foo") # error: [invalid-argument-type] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -529,7 +529,7 @@ a: int = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -563,7 +563,7 @@ C.instance_var = 3 # error: Cannot assign to instance variable Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -599,7 +599,7 @@ asyncio.run(main()) Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -623,7 +623,7 @@ class A(42): ... # error: [invalid-base] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -650,7 +650,7 @@ with 1: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -679,7 +679,7 @@ a: str Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -717,13 +717,55 @@ except ZeroDivisionError: This rule corresponds to Ruff's [`except-with-non-exception-classes` (`B030`)](https://docs.astral.sh/ruff/rules/except-with-non-exception-classes) +## `invalid-explicit-override` + + +Default level: error · +Added in 0.0.1-alpha.28 · +Related issues · +View source + + + +**What it does** + +Checks for methods that are decorated with `@override` but do not override any method in a superclass. + +**Why is this bad?** + +Decorating a method with `@override` declares to the type checker that the intention is that it should +override a method from a superclass. + +**Example** + + +```python +from typing import override + +class A: + @override + def foo(self): ... # Error raised here + +class B(A): + @override + def ffooo(self): ... # Error raised here + +class C: + @override + def __repr__(self): ... # fine: overrides `object.__repr__` + +class D(A): + @override + def foo(self): ... # fine: overrides `A.foo` +``` + ## `invalid-generic-class` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -756,7 +798,7 @@ class C[U](Generic[T]): ... Default level: error · Added in 0.0.1-alpha.17 · Related issues · -View source +View source @@ -795,7 +837,7 @@ carol = Person(name="Carol", age=25) # typo! Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -830,7 +872,7 @@ def f(t: TypeVar("U")): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -858,13 +900,99 @@ class B(metaclass=f): ... - [Python documentation: Metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) +## `invalid-method-override` + + +Default level: error · +Added in 0.0.1-alpha.20 · +Related issues · +View source + + + +**What it does** + +Detects method overrides that violate the [Liskov Substitution Principle] ("LSP"). + +The LSP states that an instance of a subtype should be substitutable for an instance of its supertype. +Applied to Python, this means: +1. All argument combinations a superclass method accepts + must also be accepted by an overriding subclass method. +2. The return type of an overriding subclass method must be a subtype + of the return type of the superclass method. + +**Why is this bad?** + +Violating the Liskov Substitution Principle will lead to many of ty's assumptions and +inferences being incorrect, which will mean that it will fail to catch many possible +type errors in your code. + +**Example** + +```python +class Super: + def method(self, x) -> int: + return 42 + +class Sub(Super): + # Liskov violation: `str` is not a subtype of `int`, + # but the supertype method promises to return an `int`. + def method(self, x) -> str: # error: [invalid-override] + return "foo" + +def accepts_super(s: Super) -> int: + return s.method(x=42) + +accepts_super(Sub()) # The result of this call is a string, but ty will infer + # it to be an `int` due to the violation of the Liskov Substitution Principle. + +class Sub2(Super): + # Liskov violation: the superclass method can be called with a `x=` + # keyword argument, but the subclass method does not accept it. + def method(self, y) -> int: # error: [invalid-override] + return 42 + +accepts_super(Sub2()) # TypeError at runtime: method() got an unexpected keyword argument 'x' + # ty cannot catch this error due to the violation of the Liskov Substitution Principle. +``` + +**Common issues** + + +**Why does ty complain about my `__eq__` method?** + + +`__eq__` and `__ne__` methods in Python are generally expected to accept arbitrary +objects as their second argument, for example: + +```python +class A: + x: int + + def __eq__(self, other: object) -> bool: + # gracefully handle an object of an unexpected type + # without raising an exception + if not isinstance(other, A): + return False + return self.x == other.x +``` + +If `A.__eq__` here were annotated as only accepting `A` instances for its second argument, +it would imply that you wouldn't be able to use `==` between instances of `A` and +instances of unrelated classes without an exception possibly being raised. While some +classes in Python do indeed behave this way, the strongly held convention is that it should +be avoided wherever possible. As part of this check, therefore, ty enforces that `__eq__` +and `__ne__` methods accept `object` as their second argument. + +[Liskov Substitution Principle]: https://en.wikipedia.org/wiki/Liskov_substitution_principle + ## `invalid-named-tuple` Default level: error · Added in 0.0.1-alpha.19 · Related issues · -View source +View source @@ -896,7 +1024,7 @@ TypeError: can only inherit from a NamedTuple type and Generic Default level: error · Preview (since 1.0.0) · Related issues · -View source +View source @@ -926,7 +1054,7 @@ Baz = NewType("Baz", int | str) # error: invalid base for `typing.NewType` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -976,7 +1104,7 @@ def foo(x: int) -> int: ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1002,7 +1130,7 @@ def f(a: int = ''): ... Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1033,7 +1161,7 @@ P2 = ParamSpec("S2") # error: ParamSpec name must match the variable it's assig Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1067,7 +1195,7 @@ TypeError: Protocols can only inherit from other protocols, got Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1116,7 +1244,7 @@ def g(): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1141,7 +1269,7 @@ def func() -> int: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1199,7 +1327,7 @@ TODO #14889 Default level: error · Added in 0.0.1-alpha.6 · Related issues · -View source +View source @@ -1226,7 +1354,7 @@ NewAlias = TypeAliasType(get_name(), int) # error: TypeAliasType name mus Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1256,7 +1384,7 @@ TYPE_CHECKING = '' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1286,7 +1414,7 @@ b: Annotated[int] # `Annotated` expects at least two arguments Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1320,7 +1448,7 @@ f(10) # Error Default level: error · Added in 0.0.1-alpha.11 · Related issues · -View source +View source @@ -1354,7 +1482,7 @@ class C: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1389,7 +1517,7 @@ T = TypeVar('T', bound=str) # valid bound TypeVar Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1414,7 +1542,7 @@ func() # TypeError: func() missing 1 required positional argument: 'x' Default level: error · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -1447,7 +1575,7 @@ alice["age"] # KeyError Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1476,7 +1604,7 @@ func("string") # error: [no-matching-overload] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1500,7 +1628,7 @@ Subscripting an object that does not support it will raise a `TypeError` at runt Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1526,7 +1654,7 @@ for i in 34: # TypeError: 'int' object is not iterable Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1553,7 +1681,7 @@ f(1, x=2) # Error raised here Default level: error · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -1611,7 +1739,7 @@ def test(): -> "int": Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1641,7 +1769,7 @@ static_assert(int(2.0 * 3.0) == 6) # error: does not have a statically known tr Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1670,7 +1798,7 @@ class B(A): ... # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1697,7 +1825,7 @@ f("foo") # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1725,7 +1853,7 @@ def _(x: int): Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1771,7 +1899,7 @@ class A: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1798,7 +1926,7 @@ f(x=1, y=2) # Error raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1826,7 +1954,7 @@ A().foo # AttributeError: 'A' object has no attribute 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1851,7 +1979,7 @@ import foo # ModuleNotFoundError: No module named 'foo' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1876,7 +2004,7 @@ print(x) # NameError: name 'x' is not defined Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1913,7 +2041,7 @@ b1 < b2 < b1 # exception raised here Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1941,7 +2069,7 @@ A() + A() # TypeError: unsupported operand type(s) for +: 'A' and 'A' Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -1966,7 +2094,7 @@ l[1:10:0] # ValueError: slice step cannot be zero Default level: warn · Added in 0.0.1-alpha.20 · Related issues · -View source +View source @@ -2007,7 +2135,7 @@ class SubProto(BaseProto, Protocol): Default level: warn · Added in 0.0.1-alpha.16 · Related issues · -View source +View source @@ -2034,7 +2162,7 @@ old_func() # emits [deprecated] diagnostic Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2065,7 +2193,7 @@ a = 20 / 0 # ty: ignore[division-by-zero] Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2095,7 +2223,7 @@ a = 20 / 0 # type: ignore Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2123,7 +2251,7 @@ A.c # AttributeError: type object 'A' has no attribute 'c' Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2155,7 +2283,7 @@ A()[0] # TypeError: 'A' object is not subscriptable Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2187,7 +2315,7 @@ from module import a # ImportError: cannot import name 'a' from 'module' Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2214,7 +2342,7 @@ cast(int, f()) # Redundant Default level: warn · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2238,7 +2366,7 @@ reveal_type(1) # NameError: name 'reveal_type' is not defined Default level: warn · Added in 0.0.1-alpha.15 · Related issues · -View source +View source @@ -2296,7 +2424,7 @@ def g(): Default level: warn · Added in 0.0.1-alpha.7 · Related issues · -View source +View source @@ -2335,7 +2463,7 @@ class D(C): ... # error: [unsupported-base] Default level: warn · Added in 0.0.1-alpha.22 · Related issues · -View source +View source @@ -2398,7 +2526,7 @@ def foo(x: int | str) -> int | str: Default level: ignore · Preview (since 0.0.1-alpha.1) · Related issues · -View source +View source @@ -2422,7 +2550,7 @@ Dividing by zero raises a `ZeroDivisionError` at runtime. Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2450,7 +2578,7 @@ print(x) # NameError: name 'x' is not defined Default level: ignore · Added in 0.0.1-alpha.1 · Related issues · -View source +View source diff --git a/pyproject.toml b/pyproject.toml index 8e50c56f84..a5a6e3487e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ty" -version = "0.0.1a27" +version = "0.0.1a28" requires-python = ">=3.8" dependencies = [] description = "An extremely fast Python type checker, written in Rust." diff --git a/ruff b/ruff index 62343a101a..5364256190 160000 --- a/ruff +++ b/ruff @@ -1 +1 @@ -Subproject commit 62343a101a44bcd5acfd23a55beb8b2e7f701809 +Subproject commit 536425619018d5531d7e5d5f66918735a8583724 diff --git a/uv.lock b/uv.lock index 63e0103ae1..53327b1387 100644 --- a/uv.lock +++ b/uv.lock @@ -624,7 +624,7 @@ wheels = [ [[package]] name = "ty" -version = "0.0.1a27" +version = "0.0.1a28" source = { editable = "." } [package.dev-dependencies]